@glion/parser 0.14.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/LICENSE +21 -0
- package/README.md +153 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +579 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +8 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/preprocessor.d.ts +22 -0
- package/dist/preprocessor.d.ts.map +1 -0
- package/dist/processor.d.ts +4 -0
- package/dist/processor.d.ts.map +1 -0
- package/dist/tokenizer.d.ts +18 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Rethink Health
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# HL7v2 Parser
|
|
2
|
+
|
|
3
|
+
This package is a utility that takes a raw HL7v2 message as input and turns it into an HL7v2 syntax tree following the [@glion/ast](../hl7v2-ast/README.md) definition.
|
|
4
|
+
|
|
5
|
+
This utility is a low level project. It’s used in [@glion/hl7v2](../hl7v2/), which focusses on making it easier to transform content by abstracting these internals away.
|
|
6
|
+
|
|
7
|
+
## When should I use this?
|
|
8
|
+
|
|
9
|
+
If you want to handle syntax trees manually, use this. For an easier time processing content, use the [@glion/hl7v2](../hl7v2/) ecosystem instead.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Speed-first**: pull-based tokenizer, single pass, minimal object allocations.
|
|
14
|
+
- **Spec-aware**: auto-detects delimiters from MSH-1 and MSH-2.
|
|
15
|
+
- **AST design**: outputs a unist-compatible tree for use in the HL7v2 unified ecosystem.
|
|
16
|
+
- **Composable**: works directly as a unified parser plugin or as a standalone function.
|
|
17
|
+
- **Streaming-friendly**: pull-based design is ready for streaming use cases.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). In Node.js (version 16+), install with npm:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @glion/parser
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Use
|
|
28
|
+
|
|
29
|
+
### Basic Usage
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { unified } from "unified";
|
|
33
|
+
import { hl7v2Parser } from "@glion/parser";
|
|
34
|
+
|
|
35
|
+
const message = `MSH|^~\\&|SENDING_APP|SENDING_FAC|...`;
|
|
36
|
+
|
|
37
|
+
const tree = unified().use(hl7v2Parser).parse(message);
|
|
38
|
+
|
|
39
|
+
console.log(tree);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Custom Delimiters
|
|
43
|
+
|
|
44
|
+
You can specify custom delimiters for parsing non-standard HL7v2 messages. The `delimiters` option accepts a partial object, so you only need to specify the delimiters you want to override:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { unified } from "unified";
|
|
48
|
+
import { hl7v2Parser } from "@glion/parser";
|
|
49
|
+
|
|
50
|
+
// Override only the segment delimiter
|
|
51
|
+
const tree = unified()
|
|
52
|
+
.use(hl7v2Parser, {
|
|
53
|
+
delimiters: {
|
|
54
|
+
segment: "\n", // Use newline instead of carriage return
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
.parse(message);
|
|
58
|
+
|
|
59
|
+
// Override multiple delimiters
|
|
60
|
+
const customTree = unified()
|
|
61
|
+
.use(hl7v2Parser, {
|
|
62
|
+
delimiters: {
|
|
63
|
+
field: "$",
|
|
64
|
+
component: "%",
|
|
65
|
+
segment: "\n",
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
.parse(customMessage);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The default delimiters are:
|
|
72
|
+
|
|
73
|
+
- `field`: `|`
|
|
74
|
+
- `component`: `^`
|
|
75
|
+
- `repetition`: `~`
|
|
76
|
+
- `subcomponent`: `&`
|
|
77
|
+
- `escape`: `\`
|
|
78
|
+
- `segment`: `\r`
|
|
79
|
+
|
|
80
|
+
### Experimental Features
|
|
81
|
+
|
|
82
|
+
The parser supports experimental features through the `experimental` option. These features are subject to change but provide opt-in access to upcoming behaviors.
|
|
83
|
+
|
|
84
|
+
#### Empty Array Mode
|
|
85
|
+
|
|
86
|
+
By default, the parser represents empty fields with full scaffolding (Field → FieldRepetition → Component → Subcomponent with `value: ""`). The `emptyMode: 'empty'` option changes this behavior to use empty children arrays instead, making the AST more compact and easier to work with.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { unified } from "unified";
|
|
90
|
+
import { hl7v2Parser } from "@glion/parser";
|
|
91
|
+
|
|
92
|
+
// With empty-array mode (new behavior)
|
|
93
|
+
const tree = unified()
|
|
94
|
+
.use(hl7v2Parser, {
|
|
95
|
+
experimental: {
|
|
96
|
+
emptyMode: "empty-array",
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
.parse("PID|1||");
|
|
100
|
+
|
|
101
|
+
// PID.2 (empty field) will have: { type: 'field', children: [] }
|
|
102
|
+
// Instead of: Field → Rep → Comp → Sub with value: ""
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Spec Rules:**
|
|
106
|
+
|
|
107
|
+
- **Leaf nodes** (Subcomponent, SegmentHeader) with no value: `value: ""`
|
|
108
|
+
- **Parent nodes** (Field, FieldRepetition, Component) with no content: `children: []`
|
|
109
|
+
- **Presence vs Value**:
|
|
110
|
+
- Node exists in parent's children array = position exists
|
|
111
|
+
- Node has empty children array = no value at that position
|
|
112
|
+
|
|
113
|
+
**Examples:**
|
|
114
|
+
|
|
115
|
+
| Wire Format | Legacy Mode | Empty-Array Mode |
|
|
116
|
+
| --------------- | ---------------------------------------------- | -------------------------------------- |
|
|
117
|
+
| `PID\|1\|\|` | Field → Rep → Comp → Sub("") | Field(children: []) |
|
|
118
|
+
| `PID\|1\|^\|` | Field → Rep → [Comp → Sub(""), Comp → Sub("")] | Field → Rep → [Comp[], Comp[]] |
|
|
119
|
+
| `PID\|1\|~\|` | Field → [Rep → Comp → Sub(""), Rep → ...] | Field → [Rep[], Rep[]] |
|
|
120
|
+
| `PID\|1\|ABC\|` | Field → Rep → Comp → Sub("ABC") | Field → Rep → Comp → Sub("ABC") (same) |
|
|
121
|
+
|
|
122
|
+
**Benefits:**
|
|
123
|
+
|
|
124
|
+
- 37-63% reduction in node count for messages with empty fields
|
|
125
|
+
- ~11% performance improvement for sparse messages
|
|
126
|
+
- Cleaner AST structure for visitor patterns and transformations
|
|
127
|
+
- Easier to distinguish "field exists but is empty" from "field has content"
|
|
128
|
+
|
|
129
|
+
**Note:** This feature will become the default behavior in future versions. The legacy mode will be deprecated.
|
|
130
|
+
|
|
131
|
+
## Contributing
|
|
132
|
+
|
|
133
|
+
We welcome contributions! Please see our [Contributing Guide][github-contributing] for more details.
|
|
134
|
+
|
|
135
|
+
1. Fork the repository
|
|
136
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
137
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
138
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
139
|
+
5. Open a Pull Request
|
|
140
|
+
|
|
141
|
+
## Code of Conduct
|
|
142
|
+
|
|
143
|
+
To ensure a welcoming and positive environment, we have a [Code of Conduct][github-code-of-conduct] that all contributors and participants are expected to adhere to.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
Copyright 2025 Rethink Health, SUARL. All rights reserved.
|
|
148
|
+
|
|
149
|
+
This program is licensed to you under the terms of the [MIT License](https://opensource.org/licenses/MIT). This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [LICENSE][github-license] file for details.
|
|
150
|
+
|
|
151
|
+
[github-code-of-conduct]: https://github.com/rethinkhealth/glion/blob/main/CODE_OF_CONDUCT.md
|
|
152
|
+
[github-license]: https://github.com/rethinkhealth/glion/blob/main/LICENSE
|
|
153
|
+
[github-contributing]: https://github.com/rethinkhealth/glion/blob/main/CONTRIBUTING.md
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC9D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { DEFAULT_DELIMITERS, isEmptyNode } from "@glion/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;
|
|
9
|
+
};
|
|
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;
|
|
16
|
+
};
|
|
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;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Default preprocessing pipeline.
|
|
42
|
+
*/
|
|
43
|
+
const defaultPreprocessors = [
|
|
44
|
+
stripBOM,
|
|
45
|
+
normalizeNewlines,
|
|
46
|
+
detectDelimiters
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Run the pipeline to initialize ParserContext.
|
|
50
|
+
*/
|
|
51
|
+
function runPreprocessors(ctx, steps) {
|
|
52
|
+
for (const step of steps) ctx = step(ctx);
|
|
53
|
+
return ctx;
|
|
54
|
+
}
|
|
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
|
+
*/
|
|
64
|
+
function hasStructuralContent(field) {
|
|
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;
|
|
70
|
+
}
|
|
71
|
+
function createSubcomponent(start) {
|
|
72
|
+
return {
|
|
73
|
+
position: {
|
|
74
|
+
end: start,
|
|
75
|
+
start
|
|
76
|
+
},
|
|
77
|
+
type: "subcomponent",
|
|
78
|
+
value: ""
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function createComponent(start, mode) {
|
|
82
|
+
return {
|
|
83
|
+
children: mode === "empty" ? [] : [createSubcomponent(start)],
|
|
84
|
+
position: {
|
|
85
|
+
end: start,
|
|
86
|
+
start
|
|
87
|
+
},
|
|
88
|
+
type: "component"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function createFieldRepetition(start, mode) {
|
|
92
|
+
return {
|
|
93
|
+
children: mode === "empty" ? [] : [createComponent(start, mode)],
|
|
94
|
+
position: {
|
|
95
|
+
end: start,
|
|
96
|
+
start
|
|
97
|
+
},
|
|
98
|
+
type: "field-repetition"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createField(start, mode) {
|
|
102
|
+
return {
|
|
103
|
+
children: mode === "empty" ? [] : [createFieldRepetition(start, mode)],
|
|
104
|
+
position: {
|
|
105
|
+
end: start,
|
|
106
|
+
start
|
|
107
|
+
},
|
|
108
|
+
type: "field"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createParserCore(ctx) {
|
|
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
|
+
/**
|
|
158
|
+
* Ensure there is an active segment. If delimiter tokens arrive before
|
|
159
|
+
* any segment name, lazily open a segment with an empty name.
|
|
160
|
+
*/
|
|
161
|
+
const ensureSegment = (start) => {
|
|
162
|
+
if (!seg) {
|
|
163
|
+
openSegment("", {
|
|
164
|
+
start,
|
|
165
|
+
end: start
|
|
166
|
+
});
|
|
167
|
+
justSetSegmentName = false;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const openField = (start) => {
|
|
171
|
+
ensureSegment(start);
|
|
172
|
+
field = createField(start, mode);
|
|
173
|
+
seg.children.push(field);
|
|
174
|
+
if (mode === "empty") {
|
|
175
|
+
rep = null;
|
|
176
|
+
comp = null;
|
|
177
|
+
currentSub = null;
|
|
178
|
+
} else {
|
|
179
|
+
rep = field.children[0] ?? null;
|
|
180
|
+
comp = rep?.children[0] ?? null;
|
|
181
|
+
currentSub = comp?.children[0] ?? null;
|
|
182
|
+
}
|
|
183
|
+
segmentHasContent = true;
|
|
184
|
+
};
|
|
185
|
+
const openRepetition = (start) => {
|
|
186
|
+
if (!field) {
|
|
187
|
+
openField(start);
|
|
188
|
+
if (mode === "empty") {
|
|
189
|
+
const emptyRep = createFieldRepetition(start, mode);
|
|
190
|
+
field.children.push(emptyRep);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (mode === "empty" && field.children.length === 0) {
|
|
194
|
+
const emptyRep = createFieldRepetition(start, mode);
|
|
195
|
+
field.children.push(emptyRep);
|
|
196
|
+
}
|
|
197
|
+
rep = createFieldRepetition(start, mode);
|
|
198
|
+
field.children.push(rep);
|
|
199
|
+
if (mode === "empty") {
|
|
200
|
+
comp = null;
|
|
201
|
+
currentSub = null;
|
|
202
|
+
} else {
|
|
203
|
+
comp = rep.children[0] ?? null;
|
|
204
|
+
currentSub = comp?.children[0] ?? null;
|
|
205
|
+
}
|
|
206
|
+
segmentHasContent = true;
|
|
207
|
+
};
|
|
208
|
+
const ensureComponent = (start) => {
|
|
209
|
+
if (!field) openField(start);
|
|
210
|
+
if (!rep) {
|
|
211
|
+
rep = createFieldRepetition(start, mode);
|
|
212
|
+
field.children.push(rep);
|
|
213
|
+
}
|
|
214
|
+
if (!comp) {
|
|
215
|
+
comp = createComponent(start, mode);
|
|
216
|
+
rep.children.push(comp);
|
|
217
|
+
}
|
|
218
|
+
segmentHasContent = true;
|
|
219
|
+
};
|
|
220
|
+
const openComponent = (start) => {
|
|
221
|
+
if (!field) {
|
|
222
|
+
openField(start);
|
|
223
|
+
if (mode === "empty") {
|
|
224
|
+
rep = createFieldRepetition(start, mode);
|
|
225
|
+
field.children.push(rep);
|
|
226
|
+
const emptyComp = createComponent(start, mode);
|
|
227
|
+
rep.children.push(emptyComp);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!rep) {
|
|
231
|
+
rep = createFieldRepetition(start, mode);
|
|
232
|
+
field.children.push(rep);
|
|
233
|
+
if (mode === "empty") {
|
|
234
|
+
const emptyComp = createComponent(start, mode);
|
|
235
|
+
rep.children.push(emptyComp);
|
|
236
|
+
}
|
|
237
|
+
} else if (mode === "empty" && rep.children.length === 0) {
|
|
238
|
+
const emptyComp = createComponent(start, mode);
|
|
239
|
+
rep.children.push(emptyComp);
|
|
240
|
+
}
|
|
241
|
+
comp = createComponent(start, mode);
|
|
242
|
+
rep.children.push(comp);
|
|
243
|
+
currentSub = mode === "empty" ? null : comp.children[0] ?? null;
|
|
244
|
+
segmentHasContent = true;
|
|
245
|
+
};
|
|
246
|
+
const ensureForText = (start) => {
|
|
247
|
+
if (!field) {
|
|
248
|
+
openField(start);
|
|
249
|
+
if (mode !== "empty") return;
|
|
250
|
+
}
|
|
251
|
+
if (!rep) {
|
|
252
|
+
if (mode !== "empty") {
|
|
253
|
+
openRepetition(start);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
rep = createFieldRepetition(start, mode);
|
|
257
|
+
field.children.push(rep);
|
|
258
|
+
}
|
|
259
|
+
if (!comp) {
|
|
260
|
+
if (mode !== "empty") {
|
|
261
|
+
openComponent(start);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
comp = createComponent(start, mode);
|
|
265
|
+
rep.children.push(comp);
|
|
266
|
+
}
|
|
267
|
+
if (!currentSub) {
|
|
268
|
+
currentSub = createSubcomponent(start);
|
|
269
|
+
comp.children.push(currentSub);
|
|
270
|
+
segmentHasContent = true;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
const updatePositionsToEnd = (endPos) => {
|
|
274
|
+
if (currentSub?.position) currentSub.position.end = endPos;
|
|
275
|
+
if (comp?.position) comp.position.end = endPos;
|
|
276
|
+
if (rep?.position) rep.position.end = endPos;
|
|
277
|
+
if (field?.position) field.position.end = endPos;
|
|
278
|
+
if (seg?.position) seg.position.end = endPos;
|
|
279
|
+
};
|
|
280
|
+
const processToken = (tok) => {
|
|
281
|
+
switch (tok.type) {
|
|
282
|
+
case "SEGMENT_END":
|
|
283
|
+
updatePositionsToEnd(lastContentEnd || tok.position.start);
|
|
284
|
+
dropTrailingEmptyFieldIfPresent();
|
|
285
|
+
resetState();
|
|
286
|
+
return;
|
|
287
|
+
case "FIELD_DELIM":
|
|
288
|
+
lastContentEnd = tok.position.end;
|
|
289
|
+
documentEnd = tok.position.end;
|
|
290
|
+
if (justSetSegmentName) {
|
|
291
|
+
justSetSegmentName = false;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (!field) openField(tok.position.start);
|
|
295
|
+
openField(tok.position.end);
|
|
296
|
+
return;
|
|
297
|
+
case "REPETITION_DELIM":
|
|
298
|
+
lastContentEnd = tok.position.end;
|
|
299
|
+
documentEnd = tok.position.end;
|
|
300
|
+
if (!field) openField(tok.position.start);
|
|
301
|
+
openRepetition(tok.position.end);
|
|
302
|
+
return;
|
|
303
|
+
case "COMPONENT_DELIM":
|
|
304
|
+
lastContentEnd = tok.position.end;
|
|
305
|
+
documentEnd = tok.position.end;
|
|
306
|
+
openComponent(tok.position.end);
|
|
307
|
+
return;
|
|
308
|
+
case "SUBCOMP_DELIM":
|
|
309
|
+
lastContentEnd = tok.position.end;
|
|
310
|
+
documentEnd = tok.position.end;
|
|
311
|
+
ensureComponent(tok.position.start);
|
|
312
|
+
if (mode === "empty" && comp.children.length === 0) {
|
|
313
|
+
const emptySub = createSubcomponent(tok.position.start);
|
|
314
|
+
comp.children.push(emptySub);
|
|
315
|
+
}
|
|
316
|
+
currentSub = createSubcomponent(tok.position.end);
|
|
317
|
+
comp?.children.push(currentSub);
|
|
318
|
+
segmentHasContent = true;
|
|
319
|
+
return;
|
|
320
|
+
case "TEXT": {
|
|
321
|
+
const val = tok.value ?? "";
|
|
322
|
+
if (!rootStartSet) {
|
|
323
|
+
root.position = {
|
|
324
|
+
end: tok.position.start,
|
|
325
|
+
start: tok.position.start
|
|
326
|
+
};
|
|
327
|
+
rootStartSet = true;
|
|
328
|
+
}
|
|
329
|
+
if (expectingSegmentName) {
|
|
330
|
+
openSegment(val, tok.position);
|
|
331
|
+
lastContentEnd = tok.position.end;
|
|
332
|
+
documentEnd = tok.position.end;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
ensureForText(tok.position.start);
|
|
336
|
+
if (currentSub) currentSub.value += val;
|
|
337
|
+
updatePositionsToEnd(tok.position.end);
|
|
338
|
+
lastContentEnd = tok.position.end;
|
|
339
|
+
documentEnd = tok.position.end;
|
|
340
|
+
segmentHasContent = true;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
default: throw new Error(`Unexpected token type: ${tok.type}`);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
const finalize = () => {
|
|
347
|
+
if (lastContentEnd && seg) updatePositionsToEnd(lastContentEnd);
|
|
348
|
+
dropTrailingEmptyFieldIfPresent();
|
|
349
|
+
if (seg && !segmentHasContent) root.children.pop();
|
|
350
|
+
if (root.position && documentEnd) root.position.end = documentEnd;
|
|
351
|
+
else if (!root.position) root.position = {
|
|
352
|
+
end: {
|
|
353
|
+
column: 1,
|
|
354
|
+
line: 1,
|
|
355
|
+
offset: 0
|
|
356
|
+
},
|
|
357
|
+
start: {
|
|
358
|
+
column: 1,
|
|
359
|
+
line: 1,
|
|
360
|
+
offset: 0
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
return root;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Drop only the phantom trailing empty field created by a terminal field
|
|
367
|
+
* delimiter (e.g. the last `|` in `PID|foo|`). Without this, every segment
|
|
368
|
+
* ending with `|` would gain an extra empty field on each parse cycle.
|
|
369
|
+
*/
|
|
370
|
+
function dropTrailingEmptyFieldIfPresent() {
|
|
371
|
+
if (!seg || seg.children.length === 0) return;
|
|
372
|
+
const lastChild = seg.children.at(-1);
|
|
373
|
+
if (lastChild?.type !== "field") return;
|
|
374
|
+
const lastField = lastChild;
|
|
375
|
+
if (isEmptyNode(lastField) && !hasStructuralContent(lastField)) seg.children.pop();
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
finalize,
|
|
379
|
+
processToken,
|
|
380
|
+
root
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function parseHL7v2FromIterator(tokens, ctx) {
|
|
384
|
+
const core = createParserCore(ctx);
|
|
385
|
+
for (const tok of tokens) core.processToken(tok);
|
|
386
|
+
return core.finalize();
|
|
387
|
+
}
|
|
388
|
+
//#endregion
|
|
389
|
+
//#region src/tokenizer.ts
|
|
390
|
+
const MSH_SEGMENT_START = 0;
|
|
391
|
+
const MSH_SEGMENT_END = 3;
|
|
392
|
+
const MSH_FIELD_SEPERATOR_START = 3;
|
|
393
|
+
const MSH_FIELD_SEPERATOR_END = 4;
|
|
394
|
+
const MSH_FIELD_DELIMITER_START = 4;
|
|
395
|
+
const MSH_FIELD_DELIMITER_END = 8;
|
|
396
|
+
var HL7v2Tokenizer = class {
|
|
397
|
+
input = "";
|
|
398
|
+
i = 0;
|
|
399
|
+
line = 1;
|
|
400
|
+
col = 1;
|
|
401
|
+
delims;
|
|
402
|
+
didMshBootstrap = false;
|
|
403
|
+
pendingBootstrap = null;
|
|
404
|
+
reset(ctx) {
|
|
405
|
+
this.input = ctx.input;
|
|
406
|
+
this.delims = ctx.delimiters;
|
|
407
|
+
this.i = 0;
|
|
408
|
+
this.line = 1;
|
|
409
|
+
this.col = 1;
|
|
410
|
+
this.didMshBootstrap = false;
|
|
411
|
+
this.pendingBootstrap = null;
|
|
412
|
+
if (this.input.startsWith("MSH")) {
|
|
413
|
+
const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);
|
|
414
|
+
const msh1 = this._slice(MSH_FIELD_SEPERATOR_START, MSH_FIELD_SEPERATOR_END);
|
|
415
|
+
const msh2 = this._slice(MSH_FIELD_DELIMITER_START, MSH_FIELD_DELIMITER_END);
|
|
416
|
+
this.pendingBootstrap = [
|
|
417
|
+
{
|
|
418
|
+
advance: msh.length,
|
|
419
|
+
kind: "TEXT",
|
|
420
|
+
value: msh
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
advance: 0,
|
|
424
|
+
kind: "FIELD_DELIM"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
advance: msh1.length,
|
|
428
|
+
kind: "TEXT",
|
|
429
|
+
value: msh1
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
advance: 0,
|
|
433
|
+
kind: "FIELD_DELIM"
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
advance: msh2.length,
|
|
437
|
+
kind: "TEXT",
|
|
438
|
+
value: msh2
|
|
439
|
+
}
|
|
440
|
+
];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
next() {
|
|
444
|
+
const s = this.input;
|
|
445
|
+
const n = s.length;
|
|
446
|
+
if (this.i >= n && !this.pendingBootstrap?.length) return null;
|
|
447
|
+
const start = {
|
|
448
|
+
column: this.col,
|
|
449
|
+
line: this.line,
|
|
450
|
+
offset: this.i
|
|
451
|
+
};
|
|
452
|
+
if (!this.didMshBootstrap && this.pendingBootstrap) {
|
|
453
|
+
const step = this.pendingBootstrap.shift();
|
|
454
|
+
if (step) {
|
|
455
|
+
if (step.kind === "TEXT") {
|
|
456
|
+
const take = Math.min(step.advance, n - this.i);
|
|
457
|
+
const out = step.value.slice(0, take);
|
|
458
|
+
this._fastAdvance(out);
|
|
459
|
+
if (this.pendingBootstrap.length === 0) {
|
|
460
|
+
this.pendingBootstrap = null;
|
|
461
|
+
this.didMshBootstrap = true;
|
|
462
|
+
}
|
|
463
|
+
return this._tok("TEXT", out, start);
|
|
464
|
+
}
|
|
465
|
+
this._advance(Math.min(step.advance, n - this.i));
|
|
466
|
+
if (this.pendingBootstrap.length === 0) {
|
|
467
|
+
this.pendingBootstrap = null;
|
|
468
|
+
this.didMshBootstrap = true;
|
|
469
|
+
}
|
|
470
|
+
return this._tok("FIELD_DELIM", void 0, start);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (this.i >= n) return null;
|
|
474
|
+
const ch = s.charAt(this.i);
|
|
475
|
+
if (ch === this.delims.segment) {
|
|
476
|
+
this._advance(1, true);
|
|
477
|
+
return this._tok("SEGMENT_END", void 0, start);
|
|
478
|
+
}
|
|
479
|
+
if (ch === this.delims.field) {
|
|
480
|
+
this._advance(1);
|
|
481
|
+
return this._tok("FIELD_DELIM", void 0, start);
|
|
482
|
+
}
|
|
483
|
+
if (ch === this.delims.repetition) {
|
|
484
|
+
this._advance(1);
|
|
485
|
+
return this._tok("REPETITION_DELIM", void 0, start);
|
|
486
|
+
}
|
|
487
|
+
if (ch === this.delims.component) {
|
|
488
|
+
this._advance(1);
|
|
489
|
+
return this._tok("COMPONENT_DELIM", void 0, start);
|
|
490
|
+
}
|
|
491
|
+
if (ch === this.delims.subcomponent) {
|
|
492
|
+
this._advance(1);
|
|
493
|
+
return this._tok("SUBCOMP_DELIM", void 0, start);
|
|
494
|
+
}
|
|
495
|
+
let j = this.i;
|
|
496
|
+
const cmp = this.delims.component, fld = this.delims.field, rep = this.delims.repetition, seg = this.delims.segment, sub = this.delims.subcomponent;
|
|
497
|
+
while (j < s.length) {
|
|
498
|
+
const c = s.charAt(j);
|
|
499
|
+
if (c === seg || c === fld || c === rep || c === cmp || c === sub) break;
|
|
500
|
+
j += 1;
|
|
501
|
+
}
|
|
502
|
+
const val = s.slice(this.i, j);
|
|
503
|
+
this._fastAdvance(val);
|
|
504
|
+
return this._tok("TEXT", val, start);
|
|
505
|
+
}
|
|
506
|
+
_slice(start, end) {
|
|
507
|
+
return this.input.slice(start, Math.min(end, this.input.length));
|
|
508
|
+
}
|
|
509
|
+
_advance(n, isNewline = false) {
|
|
510
|
+
this.i += n;
|
|
511
|
+
if (isNewline) {
|
|
512
|
+
this.line += 1;
|
|
513
|
+
this.col = 1;
|
|
514
|
+
} else this.col += n;
|
|
515
|
+
}
|
|
516
|
+
_fastAdvance(chunk) {
|
|
517
|
+
const parts = chunk.split(this.delims.segment);
|
|
518
|
+
if (parts.length > 1) {
|
|
519
|
+
this.line += parts.length - 1;
|
|
520
|
+
this.col = (parts.at(-1)?.length ?? 0) + 1;
|
|
521
|
+
} else this.col += chunk.length;
|
|
522
|
+
this.i += chunk.length;
|
|
523
|
+
}
|
|
524
|
+
_tok(type, value, start) {
|
|
525
|
+
return {
|
|
526
|
+
position: {
|
|
527
|
+
end: {
|
|
528
|
+
column: this.col,
|
|
529
|
+
line: this.line,
|
|
530
|
+
offset: this.i
|
|
531
|
+
},
|
|
532
|
+
start
|
|
533
|
+
},
|
|
534
|
+
type,
|
|
535
|
+
value
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
[Symbol.iterator]() {
|
|
539
|
+
return { next: () => {
|
|
540
|
+
const t = this.next();
|
|
541
|
+
if (!t) return {
|
|
542
|
+
done: true,
|
|
543
|
+
value: void 0
|
|
544
|
+
};
|
|
545
|
+
return {
|
|
546
|
+
done: false,
|
|
547
|
+
value: t
|
|
548
|
+
};
|
|
549
|
+
} };
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/parser.ts
|
|
554
|
+
function* iterateTokenizerSync(t) {
|
|
555
|
+
for (let tok = t.next(); tok; tok = t.next()) yield tok;
|
|
556
|
+
}
|
|
557
|
+
function parseHL7v2(input, opts = {}, settings) {
|
|
558
|
+
let ctx = {
|
|
559
|
+
delimiters: {
|
|
560
|
+
...DEFAULT_DELIMITERS,
|
|
561
|
+
...settings?.delimiters ?? {}
|
|
562
|
+
},
|
|
563
|
+
emptyMode: settings?.experimental?.emptyMode,
|
|
564
|
+
input
|
|
565
|
+
};
|
|
566
|
+
ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);
|
|
567
|
+
const tokenizer = new HL7v2Tokenizer();
|
|
568
|
+
tokenizer.reset(ctx);
|
|
569
|
+
return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);
|
|
570
|
+
}
|
|
571
|
+
const hl7v2Parser = function hl7v2Parser(options = {}) {
|
|
572
|
+
this.parser = (document) => {
|
|
573
|
+
return parseHL7v2(document, options, this.data("settings"));
|
|
574
|
+
};
|
|
575
|
+
};
|
|
576
|
+
//#endregion
|
|
577
|
+
export { HL7v2Tokenizer, defaultPreprocessors, detectDelimiters, hl7v2Parser, normalizeNewlines, parseHL7v2, parseHL7v2FromIterator, runPreprocessors, stripBOM };
|
|
578
|
+
|
|
579
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/preprocessor.ts","../src/processor.ts","../src/tokenizer.ts","../src/parser.ts"],"sourcesContent":["import { DEFAULT_DELIMITERS } from \"@glion/utils\";\n\nimport type { ParserContext, PreprocessorStep } from \"./types\";\n\n// PreprocessorStep is declared in `types.ts` to avoid circular imports\n\n/**\n * Step: strip UTF-8 BOM.\n */\nexport const stripBOM: PreprocessorStep = (ctx) => {\n if (ctx.input.codePointAt(0) === 0xfe_ff) {\n ctx.input = ctx.input.slice(1);\n }\n return ctx;\n};\n\n/**\n * Step: normalize newlines to the default delimiters.\n */\nexport const normalizeNewlines: PreprocessorStep = (ctx) => {\n ctx.input = ctx.input.replaceAll(/\\r?\\n/g, ctx.delimiters.segment);\n return ctx;\n};\n\n/**\n * Step: auto-detect delimiters from MSH.\n */\nexport const detectDelimiters: PreprocessorStep = (ctx) => {\n if (ctx.input.startsWith(\"MSH\")) {\n const field = ctx.input.charAt(3) || DEFAULT_DELIMITERS.field;\n const enc = ctx.input.slice(4, 8);\n const component = enc.charAt(0) || DEFAULT_DELIMITERS.component;\n const repetition = enc.charAt(1) || DEFAULT_DELIMITERS.repetition;\n const _escape = enc.charAt(2) || DEFAULT_DELIMITERS.escape;\n const subcomponent = enc.charAt(3) || DEFAULT_DELIMITERS.subcomponent;\n const { segment } = DEFAULT_DELIMITERS;\n\n ctx.delimiters = {\n component,\n escape: _escape,\n field,\n repetition,\n segment,\n subcomponent,\n };\n }\n return ctx;\n};\n\n/**\n * Default preprocessing pipeline.\n */\nexport const defaultPreprocessors: PreprocessorStep[] = [\n stripBOM,\n normalizeNewlines,\n detectDelimiters,\n];\n\n/**\n * Run the pipeline to initialize ParserContext.\n */\nexport function runPreprocessors(\n ctx: ParserContext,\n steps: PreprocessorStep[]\n): ParserContext {\n for (const step of steps) {\n // oxlint-disable-next-line no-param-reassign\n ctx = step(ctx);\n }\n\n return ctx;\n}\n","import type {\n Component,\n Field,\n FieldRepetition,\n Root,\n Segment,\n Subcomponent,\n} from \"@glion/ast\";\nimport { isEmptyNode } from \"@glion/utils\";\nimport type { Position } from \"unist\";\n\nimport type { ParserContext, Token } from \"./types\";\n\n/**\n * Check if a field has structural content beyond a single empty value.\n * A field with multiple repetitions, multiple components, or multiple\n * subcomponents has structural content even if all values are empty.\n * This is used to prevent dropping trailing fields like |^| or |&|\n * that have meaningful delimiter structure.\n */\nfunction hasStructuralContent(field: Field): boolean {\n if (field.children.length > 1) {\n return true;\n }\n const rep = field.children[0];\n if (rep && rep.children.length > 1) {\n return true;\n }\n const comp = rep?.children[0];\n return comp !== undefined && comp.children.length > 1;\n}\n\n// Helper to create an empty subcomponent at a given position\nfunction createSubcomponent(start: Position[\"start\"]): Subcomponent {\n return {\n position: { end: start, start },\n type: \"subcomponent\",\n value: \"\",\n };\n}\n\n// Helper to create a component (with or without children based on mode)\nfunction createComponent(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\" | undefined\n): Component {\n return {\n children: mode === \"empty\" ? [] : [createSubcomponent(start)],\n position: { end: start, start },\n type: \"component\",\n };\n}\n\n// Helper to create a field repetition (with or without children based on mode)\nfunction createFieldRepetition(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\" | undefined\n): FieldRepetition {\n return {\n children: mode === \"empty\" ? [] : [createComponent(start, mode)],\n position: { end: start, start },\n type: \"field-repetition\",\n };\n}\n\n// Helper to create a field (with or without children based on mode)\nfunction createField(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\" | undefined\n): Field {\n return {\n children: mode === \"empty\" ? [] : [createFieldRepetition(start, mode)],\n position: { end: start, start },\n type: \"field\",\n };\n}\n\n// Shared core: process a single token into mutable parse state\nfunction createParserCore(ctx: ParserContext) {\n const mode = ctx.emptyMode;\n const root: Root = {\n children: [],\n type: \"root\",\n };\n\n let seg: Segment | null = null;\n let field: Field | null = null;\n let rep: FieldRepetition | null = null;\n let comp: Component | null = null;\n let currentSub: Subcomponent | null = null;\n let segmentHasContent = false;\n let lastContentEnd: Position[\"end\"] | null = null;\n let documentEnd: Position[\"end\"] | null = null; // Track document-level end position\n let expectingSegmentName = true; // Start expecting a segment name\n let justSetSegmentName = false; // Track if we just set the segment name\n let rootStartSet = false; // Track if we've set the root start position\n\n const resetState = () => {\n seg = null;\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n lastContentEnd = null;\n expectingSegmentName = true;\n };\n\n const openSegment = (name: string, position: Position) => {\n seg = {\n children: [],\n name,\n position: { end: position.end, start: position.start },\n type: \"segment\",\n };\n root.children.push(seg);\n // Reset field-level state\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n justSetSegmentName = true;\n expectingSegmentName = false;\n };\n\n /**\n * Ensure there is an active segment. If delimiter tokens arrive before\n * any segment name, lazily open a segment with an empty name.\n */\n const ensureSegment = (start: Position[\"start\"]) => {\n if (!seg) {\n openSegment(\"\", {\n start,\n end: start,\n });\n // Reset justSetSegmentName so the first FIELD_DELIM isn't skipped\n // (there's no real segment name to separate from fields)\n justSetSegmentName = false;\n }\n };\n\n const openField = (start: Position[\"start\"]) => {\n ensureSegment(start);\n field = createField(start, mode);\n seg!.children.push(field);\n if (mode === \"empty\") {\n rep = null;\n comp = null;\n currentSub = null;\n } else {\n rep = field.children[0] ?? null;\n comp = rep?.children[0] ?? null;\n currentSub = comp?.children[0] ?? null;\n }\n segmentHasContent = true;\n };\n\n const openRepetition = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // In empty-array mode, openField creates empty field\n // We need to add an empty first repetition before the delimiter\n if (mode === \"empty\") {\n const emptyRep = createFieldRepetition(start, mode);\n field!.children.push(emptyRep);\n }\n // Fall through to create the repetition after the delimiter\n }\n // In empty-array mode, if field has no children yet, add empty first rep\n if (mode === \"empty\" && field!.children.length === 0) {\n const emptyRep = createFieldRepetition(start, mode);\n field!.children.push(emptyRep);\n }\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n if (mode === \"empty\") {\n comp = null;\n currentSub = null;\n } else {\n comp = rep.children[0] ?? null;\n currentSub = comp?.children[0] ?? null;\n }\n segmentHasContent = true;\n };\n\n // Ensure there's a component to add subcomponents to (for SUBCOMP_DELIM)\n // Does NOT add empty sibling components\n const ensureComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n }\n if (!rep) {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n }\n if (!comp) {\n comp = createComponent(start, mode);\n rep.children.push(comp);\n }\n segmentHasContent = true;\n };\n\n // Handle COMPONENT_DELIM - adds empty component before delimiter if needed\n const openComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // In empty-array mode, openField creates empty field\n // We need to create rep and add empty first component\n if (mode === \"empty\") {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n // Fall through to create the component after the delimiter\n }\n if (!rep) {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n // In empty-array mode, need to add empty first component before the delimiter\n if (mode === \"empty\") {\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n } else if (mode === \"empty\" && rep.children.length === 0) {\n // If rep exists but has no children, add empty first component\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n comp = createComponent(start, mode);\n rep.children.push(comp);\n currentSub = mode === \"empty\" ? null : (comp.children[0] ?? null);\n segmentHasContent = true;\n };\n\n const ensureForText = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n if (mode !== \"empty\") {\n return; // Everything is already set up by openField in legacy mode\n }\n // In empty-array mode, openField created empty field, need to build structure\n }\n if (!rep) {\n if (mode !== \"empty\") {\n openRepetition(start);\n return; // Everything is already set up by openRepetition in legacy mode\n }\n // In empty-array mode, need to create repetition\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n }\n if (!comp) {\n if (mode !== \"empty\") {\n openComponent(start);\n return; // Everything is already set up by openComponent in legacy mode\n }\n // In empty-array mode, need to create component\n comp = createComponent(start, mode);\n rep!.children.push(comp);\n }\n if (!currentSub) {\n currentSub = createSubcomponent(start);\n comp!.children.push(currentSub);\n segmentHasContent = true;\n }\n };\n\n const updatePositionsToEnd = (endPos: Position[\"end\"]) => {\n if (currentSub?.position) {\n currentSub.position.end = endPos;\n }\n if (comp?.position) {\n comp.position.end = endPos;\n }\n if (rep?.position) {\n rep.position.end = endPos;\n }\n if (field?.position) {\n field.position.end = endPos;\n }\n if (seg?.position) {\n seg.position.end = endPos;\n }\n };\n\n const processToken = (tok: Token) => {\n switch (tok.type) {\n case \"SEGMENT_END\": {\n // Use the last content position instead of the segment delimiter position\n const endPos = lastContentEnd || tok.position.start;\n updatePositionsToEnd(endPos);\n dropTrailingEmptyFieldIfPresent();\n resetState();\n return;\n }\n case \"FIELD_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Skip the first field delimiter after setting segment name (it separates segment name from fields)\n if (justSetSegmentName) {\n justSetSegmentName = false;\n return;\n }\n // Leading field delimiter implies an empty first field\n if (!field) {\n // Open an empty first field (already has empty subcomponent from openField)\n openField(tok.position.start);\n }\n openField(tok.position.end);\n return;\n }\n case \"REPETITION_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n if (!field) {\n openField(tok.position.start);\n }\n openRepetition(tok.position.end);\n return;\n }\n case \"COMPONENT_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n openComponent(tok.position.end);\n return;\n }\n case \"SUBCOMP_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Ensure there's a component to add subcomponents to\n ensureComponent(tok.position.start);\n // In empty-array mode, if comp has no children yet, add empty first subcomponent\n if (mode === \"empty\" && comp!.children.length === 0) {\n const emptySub = createSubcomponent(tok.position.start);\n comp!.children.push(emptySub);\n }\n currentSub = createSubcomponent(tok.position.end);\n comp?.children.push(currentSub);\n segmentHasContent = true;\n return;\n }\n case \"TEXT\": {\n const val = tok.value ?? \"\";\n\n // Set root start position on first token\n if (!rootStartSet) {\n root.position = {\n end: tok.position.start,\n start: tok.position.start,\n };\n rootStartSet = true;\n }\n\n // If we're expecting a segment name, this TEXT is the segment name\n if (expectingSegmentName) {\n // `tok.position` is always available for TEXT tokens\n openSegment(val, tok.position);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n return;\n }\n\n // Otherwise, it's regular field content\n ensureForText(tok.position.start);\n if (currentSub) {\n currentSub.value += val;\n }\n updatePositionsToEnd(tok.position.end);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n segmentHasContent = true;\n return;\n }\n default: {\n throw new Error(`Unexpected token type: ${tok.type}`);\n }\n }\n };\n\n // Do not pre-open a segment; lazily open upon first non-SEGMENT_END token.\n\n const finalize = () => {\n // Handle input that ends without an explicit segment delimiter as above.\n if (lastContentEnd && seg) {\n updatePositionsToEnd(lastContentEnd);\n }\n dropTrailingEmptyFieldIfPresent();\n if (seg && !segmentHasContent) {\n // Drop trailing empty segment if no content was added\n root.children.pop();\n }\n\n // Set root end position once at finalization using document-level end\n if (root.position && documentEnd) {\n root.position.end = documentEnd;\n } else if (!root.position) {\n // Empty document - set default position\n root.position = {\n end: { column: 1, line: 1, offset: 0 },\n start: { column: 1, line: 1, offset: 0 },\n };\n }\n\n return root;\n };\n\n /**\n * Drop only the phantom trailing empty field created by a terminal field\n * delimiter (e.g. the last `|` in `PID|foo|`). Without this, every segment\n * ending with `|` would gain an extra empty field on each parse cycle.\n */\n function dropTrailingEmptyFieldIfPresent() {\n if (!seg || seg.children.length === 0) {\n return;\n }\n const lastChild = seg.children.at(-1);\n if (lastChild?.type !== \"field\") {\n return;\n }\n const lastField = lastChild as Field;\n // Both conditions are needed: a field like `|^|` is isEmpty (no leaf data)\n // but has structural content (component separators) and must be preserved.\n if (isEmptyNode(lastField) && !hasStructuralContent(lastField)) {\n seg.children.pop();\n }\n }\n\n return { finalize, processToken, root };\n}\n\n// Sync convenience wrapper over a sync Iterable token source\nexport function parseHL7v2FromIterator(\n tokens: Iterable<Token>,\n ctx: ParserContext\n): Root {\n const core = createParserCore(ctx);\n for (const tok of tokens) {\n core.processToken(tok);\n }\n return core.finalize();\n}\n","// src/tokenizer.ts\nimport type { Delimiters } from \"@glion/ast\";\nimport type { Position } from \"unist\";\n\nimport type { ParserContext, Token, Tokenizer, TokenType } from \"./types\";\n\nconst MSH_SEGMENT_START = 0;\nconst MSH_SEGMENT_END = 3;\nconst MSH_FIELD_SEPERATOR_START = 3;\nconst MSH_FIELD_SEPERATOR_END = 4;\nconst MSH_FIELD_DELIMITER_START = 4;\nconst MSH_FIELD_DELIMITER_END = 8;\n\nexport class HL7v2Tokenizer implements Tokenizer, Iterable<Token> {\n private input = \"\";\n private i = 0;\n private line = 1;\n private col = 1;\n private delims!: Delimiters;\n\n // Only-run-once MSH bootstrap at the start of the file\n private didMshBootstrap = false;\n private pendingBootstrap: null | Array<\n | { kind: \"TEXT\"; value: string; advance: number }\n | { kind: \"FIELD_DELIM\"; advance: number }\n > = null; // queue to emit TEXT('MSH'), FIELD_DELIM, TEXT('^~\\\\&')\n\n reset(ctx: ParserContext) {\n this.input = ctx.input;\n this.delims = ctx.delimiters;\n this.i = 0;\n this.line = 1;\n this.col = 1;\n this.didMshBootstrap = false;\n this.pendingBootstrap = null;\n\n // Prepare a one-time bootstrap if file starts with MSH\n if (this.input.startsWith(\"MSH\")) {\n // Precompute MSH and MSH.2; MSH.1 is the field delimiter char at index 3\n const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);\n const msh1 = this._slice(\n MSH_FIELD_SEPERATOR_START,\n MSH_FIELD_SEPERATOR_END\n ); // the field delimiter char at index 3\n const msh2 = this._slice(\n MSH_FIELD_DELIMITER_START,\n MSH_FIELD_DELIMITER_END\n ); // may be shorter than 4 if truncated\n this.pendingBootstrap = [\n { advance: msh.length, kind: \"TEXT\", value: msh },\n { advance: 0, kind: \"FIELD_DELIM\" },\n { advance: msh1.length, kind: \"TEXT\", value: msh1 },\n { advance: 0, kind: \"FIELD_DELIM\" },\n { advance: msh2.length, kind: \"TEXT\", value: msh2 },\n ];\n // NOTE: we have not advanced indices yet; we will as we emit tokens\n }\n }\n\n // oxlint-disable-next-line complexity\n next(): Token | null {\n const s = this.input;\n const n = s.length;\n if (this.i >= n && !this.pendingBootstrap?.length) {\n return null;\n }\n\n const start = { column: this.col, line: this.line, offset: this.i };\n\n // ---- One-time MSH bootstrap at file start ----\n if (!this.didMshBootstrap && this.pendingBootstrap) {\n const step = this.pendingBootstrap.shift();\n if (step) {\n if (step.kind === \"TEXT\") {\n const take = Math.min(step.advance, n - this.i);\n const out = step.value.slice(0, take);\n this._fastAdvance(out);\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"TEXT\", out, start);\n }\n // FIELD_DELIM: advance exactly one char (the delimiter) and emit a FIELD_DELIM token\n this._advance(Math.min(step.advance, n - this.i));\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n }\n\n // ---- Normal tokenization (delimiters + text) ----\n if (this.i >= n) {\n return null;\n }\n const ch = s.charAt(this.i);\n\n if (ch === this.delims.segment) {\n this._advance(1, true);\n return this._tok(\"SEGMENT_END\", undefined, start);\n }\n if (ch === this.delims.field) {\n this._advance(1);\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n if (ch === this.delims.repetition) {\n this._advance(1);\n return this._tok(\"REPETITION_DELIM\", undefined, start);\n }\n if (ch === this.delims.component) {\n this._advance(1);\n return this._tok(\"COMPONENT_DELIM\", undefined, start);\n }\n if (ch === this.delims.subcomponent) {\n this._advance(1);\n return this._tok(\"SUBCOMP_DELIM\", undefined, start);\n }\n\n // TEXT until next delimiter or end\n let j = this.i;\n const cmp = this.delims.component,\n fld = this.delims.field,\n rep = this.delims.repetition,\n seg = this.delims.segment,\n sub = this.delims.subcomponent;\n\n while (j < s.length) {\n const c = s.charAt(j);\n if (c === seg || c === fld || c === rep || c === cmp || c === sub) {\n break;\n }\n j += 1;\n }\n\n const val = s.slice(this.i, j);\n this._fastAdvance(val);\n return this._tok(\"TEXT\", val, start);\n }\n\n // ---- helpers ----\n private _slice(start: number, end: number): string {\n return this.input.slice(start, Math.min(end, this.input.length));\n }\n\n private _advance(n: number, isNewline = false) {\n this.i += n;\n if (isNewline) {\n this.line += 1;\n this.col = 1;\n } else {\n this.col += n;\n }\n }\n\n private _fastAdvance(chunk: string) {\n const parts = chunk.split(this.delims.segment);\n if (parts.length > 1) {\n this.line += parts.length - 1;\n this.col = (parts.at(-1)?.length ?? 0) + 1;\n } else {\n this.col += chunk.length;\n }\n this.i += chunk.length;\n }\n\n private _tok(\n type: TokenType,\n value: string | undefined,\n start: Position[\"start\"]\n ): Token {\n const end = { column: this.col, line: this.line, offset: this.i };\n return { position: { end, start }, type, value };\n }\n\n // Iterable protocol (sync)\n [Symbol.iterator](): Iterator<Token> {\n return {\n next: () => {\n const t = this.next();\n if (!t) {\n return { done: true, value: undefined as unknown as Token };\n }\n return { done: false, value: t };\n },\n };\n }\n}\n","import type { Root } from \"@glion/ast\";\nimport type { HL7v2Settings } from \"@glion/config\";\nimport { DEFAULT_DELIMITERS } from \"@glion/utils\";\nimport type { Plugin, Processor } from \"unified\";\n\nimport { defaultPreprocessors, runPreprocessors } from \"./preprocessor\";\nimport { parseHL7v2FromIterator } from \"./processor\";\nimport { HL7v2Tokenizer } from \"./tokenizer\";\nimport type { ParseOptions, ParserContext, Token, Tokenizer } from \"./types\";\n\nfunction* iterateTokenizerSync(t: Tokenizer): Iterable<Token> {\n for (let tok = t.next(); tok; tok = t.next()) {\n yield tok;\n }\n}\n\nexport function parseHL7v2(\n input: string,\n opts: ParseOptions = {},\n settings?: HL7v2Settings\n): Root {\n // Merge settings: defaults < provided settings\n // Note: MSH auto-detection (via detectDelimiters preprocessor) runs after this\n // and will override delimiters if the message starts with MSH\n\n let ctx: ParserContext = {\n delimiters: {\n ...DEFAULT_DELIMITERS,\n ...(settings?.delimiters ?? {}),\n },\n emptyMode: settings?.experimental?.emptyMode,\n input,\n };\n // Run preprocessing\n ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);\n // Run tokenizer\n const tokenizer = new HL7v2Tokenizer();\n // Reset tokenizer.\n tokenizer.reset(ctx);\n // Parse\n return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);\n}\n\nconst hl7v2Parser: Plugin<[ParseOptions?], string, Root> = function hl7v2Parser(\n this: Processor,\n options = {}\n) {\n this.parser = (document: string) => {\n // Get settings from processor (populated by unified-args from config)\n const settings = this.data(\"settings\");\n\n return parseHL7v2(document, options, settings);\n };\n};\n\nexport default hl7v2Parser;\n"],"mappings":"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"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Root } from "@glion/ast";
|
|
2
|
+
import type { HL7v2Settings } from "@glion/config";
|
|
3
|
+
import type { Plugin } from "unified";
|
|
4
|
+
import type { ParseOptions } from "./types";
|
|
5
|
+
export declare function parseHL7v2(input: string, opts?: ParseOptions, settings?: HL7v2Settings): Root;
|
|
6
|
+
declare const hl7v2Parser: Plugin<[ParseOptions?], string, Root>;
|
|
7
|
+
export default hl7v2Parser;
|
|
8
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,SAAS,CAAC;AAKjD,OAAO,KAAK,EAAE,YAAY,EAAmC,MAAM,SAAS,CAAC;AAQ7E,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,YAAiB,EACvB,QAAQ,CAAC,EAAE,aAAa,GACvB,IAAI,CAqBN;AAED,QAAA,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAUtD,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ParserContext, PreprocessorStep } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Step: strip UTF-8 BOM.
|
|
4
|
+
*/
|
|
5
|
+
export declare const stripBOM: PreprocessorStep;
|
|
6
|
+
/**
|
|
7
|
+
* Step: normalize newlines to the default delimiters.
|
|
8
|
+
*/
|
|
9
|
+
export declare const normalizeNewlines: PreprocessorStep;
|
|
10
|
+
/**
|
|
11
|
+
* Step: auto-detect delimiters from MSH.
|
|
12
|
+
*/
|
|
13
|
+
export declare const detectDelimiters: PreprocessorStep;
|
|
14
|
+
/**
|
|
15
|
+
* Default preprocessing pipeline.
|
|
16
|
+
*/
|
|
17
|
+
export declare const defaultPreprocessors: PreprocessorStep[];
|
|
18
|
+
/**
|
|
19
|
+
* Run the pipeline to initialize ParserContext.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runPreprocessors(ctx: ParserContext, steps: PreprocessorStep[]): ParserContext;
|
|
22
|
+
//# sourceMappingURL=preprocessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preprocessor.d.ts","sourceRoot":"","sources":["../src/preprocessor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAI/D;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,gBAKtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,gBAG/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,gBAoB9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,gBAAgB,EAIlD,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE,gBAAgB,EAAE,GACxB,aAAa,CAOf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,IAAI,EAGL,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAsapD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,EACvB,GAAG,EAAE,aAAa,GACjB,IAAI,CAMN"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ParserContext, Token, Tokenizer } from "./types";
|
|
2
|
+
export declare class HL7v2Tokenizer implements Tokenizer, Iterable<Token> {
|
|
3
|
+
private input;
|
|
4
|
+
private i;
|
|
5
|
+
private line;
|
|
6
|
+
private col;
|
|
7
|
+
private delims;
|
|
8
|
+
private didMshBootstrap;
|
|
9
|
+
private pendingBootstrap;
|
|
10
|
+
reset(ctx: ParserContext): void;
|
|
11
|
+
next(): Token | null;
|
|
12
|
+
private _slice;
|
|
13
|
+
private _advance;
|
|
14
|
+
private _fastAdvance;
|
|
15
|
+
private _tok;
|
|
16
|
+
[Symbol.iterator](): Iterator<Token>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=tokenizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer.d.ts","sourceRoot":"","sources":["../src/tokenizer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAa,MAAM,SAAS,CAAC;AAS1E,qBAAa,cAAe,YAAW,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC/D,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,CAAC,CAAK;IACd,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,MAAM,CAAc;IAG5B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAGf;IAET,KAAK,CAAC,GAAG,EAAE,aAAa;IAiCxB,IAAI,IAAI,KAAK,GAAG,IAAI;IAkFpB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,IAAI;IAUZ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC;CAWrC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Delimiters } from "@glion/ast";
|
|
2
|
+
import type { Position } from "unist";
|
|
3
|
+
export type PreprocessorStep = (ctx: ParserContext) => ParserContext;
|
|
4
|
+
export interface ParseOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Optional preprocessing steps to apply to the input before parsing.
|
|
7
|
+
*/
|
|
8
|
+
preprocess?: PreprocessorStep[];
|
|
9
|
+
}
|
|
10
|
+
export interface ParserContext {
|
|
11
|
+
input: string;
|
|
12
|
+
delimiters: Delimiters;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
emptyMode?: "legacy" | "empty";
|
|
15
|
+
}
|
|
16
|
+
export interface Token {
|
|
17
|
+
type: TokenType;
|
|
18
|
+
value?: string;
|
|
19
|
+
position: Position;
|
|
20
|
+
}
|
|
21
|
+
export type TokenType = "SEGMENT_END" | "FIELD_DELIM" | "REPETITION_DELIM" | "COMPONENT_DELIM" | "SUBCOMP_DELIM" | "TEXT";
|
|
22
|
+
export interface Tokenizer {
|
|
23
|
+
reset(ctx: ParserContext): void;
|
|
24
|
+
next(): Token | null;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,aAAa,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAChC;AAGD,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAGD,MAAM,MAAM,SAAS,GACjB,aAAa,GACb,aAAa,GACb,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,MAAM,CAAC;AAEX,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC;CACtB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glion/parser",
|
|
3
|
+
"version": "0.14.1",
|
|
4
|
+
"description": "hl7v2 plugin to parse hl7v2 messages",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"health",
|
|
7
|
+
"healthcare",
|
|
8
|
+
"hl7",
|
|
9
|
+
"hl7v2",
|
|
10
|
+
"nodejs",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://glion.dev",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Melek Somai",
|
|
17
|
+
"email": "melek@rethinkhealth.io"
|
|
18
|
+
},
|
|
19
|
+
"repository": "rethinkhealth/glion.git",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"unified": "11.0.5",
|
|
33
|
+
"@glion/utils": "0.14.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.5.0",
|
|
37
|
+
"@types/unist": "^3.0.3",
|
|
38
|
+
"@vitest/coverage-v8": "4.1.2",
|
|
39
|
+
"tsdown": "0.21.7",
|
|
40
|
+
"typescript": "^5.9.3",
|
|
41
|
+
"unist": "^0.0.1",
|
|
42
|
+
"vitest": "4.1.2",
|
|
43
|
+
"@glion/testing": "0.0.2",
|
|
44
|
+
"@glion/ast": "0.14.1",
|
|
45
|
+
"@glion/config": "0.14.1",
|
|
46
|
+
"@glion/tsconfig": "0.0.1"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"bench": "vitest bench --run",
|
|
53
|
+
"build": "tsdown && tsc --emitDeclarationOnly",
|
|
54
|
+
"check-types": "tsc --noEmit",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"test:watch": "vitest"
|
|
58
|
+
}
|
|
59
|
+
}
|