@glion/util-query 0.25.0 → 0.27.0
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 +1 -1
- package/README.md +90 -65
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/format.d.ts +9 -8
- package/dist/format.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +306 -584
- package/dist/index.js.map +1 -1
- package/dist/matches.d.ts +8 -8
- package/dist/matches.d.ts.map +1 -1
- package/dist/parse.d.ts +3 -5
- package/dist/parse.d.ts.map +1 -1
- package/dist/resolve.d.ts +27 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/select.d.ts +28 -31
- package/dist/select.d.ts.map +1 -1
- package/dist/types.d.ts +23 -29
- package/dist/types.d.ts.map +1 -1
- package/dist/value.d.ts +9 -7
- package/dist/value.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/set.d.ts +0 -35
- package/dist/set.d.ts.map +0 -1
- package/dist/utils.d.ts +0 -33
- package/dist/utils.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,103 +1,113 @@
|
|
|
1
1
|
import { DEFAULT_DELIMITERS } from "@glion/utils";
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/errors.ts
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Format:
|
|
7
|
-
* [GROUP[N]-]...[GROUP[N]-]NAME[N][-FIELD[REPETITION][.COMPONENT[.SUBCOMPONENT]]]
|
|
8
|
-
*
|
|
9
|
-
* **Design Philosophy**: Groups vs Segments are distinguished by POSITION and
|
|
10
|
-
* USAGE, not name length:
|
|
11
|
-
*
|
|
12
|
-
* - **Navigation (Groups)**: Names appearing as prefixes (before the final name)
|
|
13
|
-
* are treated as group navigation: `ORDER-ORC`, `ORDER-TIMING-TQ1`
|
|
14
|
-
* - **Target (Segments)**: The final name in the path is the query target.
|
|
15
|
-
* Whether it's a segment or group is determined by:
|
|
16
|
-
*
|
|
17
|
-
* 1. Field access (-.digit): Definitively a segment → `ORC-1`, `ORDER-ORC-1`
|
|
18
|
-
* 2. No field access: Query the AST to see what exists → `ORC`, `ORDER`
|
|
4
|
+
* The typed failure for `@glion/util-query`. Branch on `code`; each code is
|
|
5
|
+
* one cause.
|
|
19
6
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* - Makes no assumptions about name length (works with standard 3-char segments
|
|
23
|
-
* AND edge cases)
|
|
24
|
-
* - Lets the path structure indicate intent (field access = segment internals)
|
|
25
|
-
* - Allows the AST to be the source of truth for what exists
|
|
26
|
-
*
|
|
27
|
-
* Examples:
|
|
28
|
-
*
|
|
29
|
-
* - `MSH-3` → MSH is segment (has field access)
|
|
30
|
-
* - `ORDER-ORC` → ORDER is navigation, ORC is target (could be segment or nested
|
|
31
|
-
* group)
|
|
32
|
-
* - `ORDER-ORC-1` → ORDER is navigation, ORC is segment (has field access)
|
|
7
|
+
* @module
|
|
33
8
|
*/
|
|
34
|
-
const PATH_REGEX = /^((?:[A-Z][A-Z0-9_]*(?:\[\d+\])?-)*)([A-Z][A-Z0-9_]*)(?:\[(\d+)\])?(?:-(\d+)(?:(?:\[(\d+)\])?(?:\.(\d+)(?:\.(\d+))?)?)?)?$/;
|
|
35
9
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
10
|
+
* Machine-readable codes for what a path got wrong:
|
|
11
|
+
*
|
|
12
|
+
* - `EMPTY_PATH` — the path is empty.
|
|
13
|
+
* - `INVALID_PATH` — the path does not follow the path grammar.
|
|
39
14
|
*/
|
|
40
|
-
const
|
|
15
|
+
const QueryErrorCode = {
|
|
16
|
+
EMPTY_PATH: "EMPTY_PATH",
|
|
17
|
+
INVALID_PATH: "INVALID_PATH"
|
|
18
|
+
};
|
|
19
|
+
function sentence(code, path) {
|
|
20
|
+
switch (code) {
|
|
21
|
+
case "EMPTY_PATH": return "The path is empty; a path is `[GROUP[N].]...NAME[N][.FIELD[REPETITION][.COMPONENT[.SUBCOMPONENT]]]`, or `FIELD[REPETITION][.COMPONENT[.SUBCOMPONENT]]` read from a segment, with positions from 1, such as `PID.5.1` or `5.1`.";
|
|
22
|
+
case "INVALID_PATH": return `Path \`${path}\` does not follow the path grammar; a path is \`[GROUP[N].]...NAME[N][.FIELD[REPETITION][.COMPONENT[.SUBCOMPONENT]]]\`, or \`FIELD[REPETITION][.COMPONENT[.SUBCOMPONENT]]\` read from a segment, with positions from 1, such as \`PID.5.1\` or \`5.1\`.`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** A path that `@glion/util-query` cannot read. */
|
|
26
|
+
var QueryError = class extends Error {
|
|
27
|
+
name = "QueryError";
|
|
28
|
+
code;
|
|
29
|
+
path;
|
|
30
|
+
constructor(code, path) {
|
|
31
|
+
super(sentence(code, path));
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.path = path;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/parse.ts
|
|
41
38
|
/**
|
|
42
|
-
*
|
|
39
|
+
* Canonical HL7 path grammar, every step separated with `.`:
|
|
40
|
+
*
|
|
41
|
+
* [GROUP[N].]...NAME[N][.FIELD[REPETITION][.COMPONENT[.SUBCOMPONENT]]]
|
|
42
|
+
* FIELD[REPETITION][.COMPONENT[.SUBCOMPONENT]]
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* Names start with a letter and positions are numbers from 1, so the first
|
|
45
|
+
* numeric step is the field. The final name is the query target. Field
|
|
46
|
+
* access makes it a segment (`ORDER.ORC.1`); without it the AST decides
|
|
47
|
+
* whether it names a segment or a group (`ORDER.ORC`, `ORDER`). A path that
|
|
48
|
+
* starts with the field names no segment (`5.1`).
|
|
47
49
|
*/
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"MSH-12": {
|
|
54
|
-
field: 12,
|
|
55
|
-
segment: { name: "MSH" }
|
|
56
|
-
},
|
|
57
|
-
"MSH-3": {
|
|
50
|
+
const PATH_REGEX = /^(?:([A-Z][A-Z0-9_]*(?:\[[1-9]\d*\])?(?:\.[A-Z][A-Z0-9_]*(?:\[[1-9]\d*\])?)*)(?:\.(?=[1-9])|$))?(?:([1-9]\d*)(?:\[([1-9]\d*)\])?(?:\.([1-9]\d*)(?:\.([1-9]\d*))?)?)?$/;
|
|
51
|
+
const NAVIGATION_STEP_REGEX = /([A-Z][A-Z0-9_]*)(?:\[(\d+)\])?/g;
|
|
52
|
+
/** Parts of the MSH paths that plugins and lint rules read on every message. */
|
|
53
|
+
const PRECOMPUTED_PATHS = new Map([
|
|
54
|
+
["MSH.3", () => ({
|
|
58
55
|
field: 3,
|
|
59
56
|
segment: { name: "MSH" }
|
|
60
|
-
},
|
|
61
|
-
"MSH
|
|
57
|
+
})],
|
|
58
|
+
["MSH.4", () => ({
|
|
62
59
|
field: 4,
|
|
63
60
|
segment: { name: "MSH" }
|
|
64
|
-
},
|
|
65
|
-
"MSH
|
|
61
|
+
})],
|
|
62
|
+
["MSH.5", () => ({
|
|
66
63
|
field: 5,
|
|
67
64
|
segment: { name: "MSH" }
|
|
68
|
-
},
|
|
69
|
-
"MSH
|
|
65
|
+
})],
|
|
66
|
+
["MSH.6", () => ({
|
|
70
67
|
field: 6,
|
|
71
68
|
segment: { name: "MSH" }
|
|
72
|
-
},
|
|
73
|
-
"MSH
|
|
69
|
+
})],
|
|
70
|
+
["MSH.9", () => ({
|
|
74
71
|
field: 9,
|
|
75
72
|
segment: { name: "MSH" }
|
|
76
|
-
},
|
|
77
|
-
"MSH
|
|
73
|
+
})],
|
|
74
|
+
["MSH.9.1", () => ({
|
|
78
75
|
component: 1,
|
|
79
76
|
field: 9,
|
|
80
77
|
segment: { name: "MSH" }
|
|
81
|
-
},
|
|
82
|
-
"MSH
|
|
78
|
+
})],
|
|
79
|
+
["MSH.9.2", () => ({
|
|
83
80
|
component: 2,
|
|
84
81
|
field: 9,
|
|
85
82
|
segment: { name: "MSH" }
|
|
86
|
-
},
|
|
87
|
-
"MSH
|
|
83
|
+
})],
|
|
84
|
+
["MSH.9.3", () => ({
|
|
88
85
|
component: 3,
|
|
89
86
|
field: 9,
|
|
90
87
|
segment: { name: "MSH" }
|
|
91
|
-
}
|
|
92
|
-
|
|
88
|
+
})],
|
|
89
|
+
["MSH.10", () => ({
|
|
90
|
+
field: 10,
|
|
91
|
+
segment: { name: "MSH" }
|
|
92
|
+
})],
|
|
93
|
+
["MSH.12", () => ({
|
|
94
|
+
field: 12,
|
|
95
|
+
segment: { name: "MSH" }
|
|
96
|
+
})],
|
|
97
|
+
["MSH.12.1", () => ({
|
|
98
|
+
component: 1,
|
|
99
|
+
field: 12,
|
|
100
|
+
segment: { name: "MSH" }
|
|
101
|
+
})]
|
|
102
|
+
]);
|
|
93
103
|
/**
|
|
94
104
|
* Parse an HL7 path string into structured parts.
|
|
95
|
-
*
|
|
96
|
-
*
|
|
105
|
+
*
|
|
106
|
+
* Returns a new object on every call.
|
|
97
107
|
*
|
|
98
108
|
* @example
|
|
99
109
|
* ```typescript
|
|
100
|
-
* parse("PID
|
|
110
|
+
* parse("PID.5[1].2.1");
|
|
101
111
|
* // {
|
|
102
112
|
* // segment: { name: 'PID' },
|
|
103
113
|
* // field: 5,
|
|
@@ -111,387 +121,195 @@ const HOT_PATHS = {
|
|
|
111
121
|
* @returns Structured path components
|
|
112
122
|
*/
|
|
113
123
|
function parse(path) {
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
if (cached) return cached;
|
|
118
|
-
const result = parseImpl(path);
|
|
119
|
-
parseCache.set(path, result);
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Internal implementation of path parsing.
|
|
124
|
-
*/
|
|
125
|
-
function parseImpl(path) {
|
|
126
|
-
if (!path || typeof path !== "string") throw new Error(`Path must be a non-empty string, got: ${path}`);
|
|
127
|
-
if (path.trim() !== path) throw new Error(`Path cannot contain leading/trailing spaces: "${path}"`);
|
|
124
|
+
const precomputed = PRECOMPUTED_PATHS.get(path);
|
|
125
|
+
if (precomputed) return precomputed();
|
|
126
|
+
if (path === "") throw new QueryError("EMPTY_PATH", path);
|
|
128
127
|
const match = PATH_REGEX.exec(path);
|
|
129
|
-
if (!match) throw new
|
|
130
|
-
const [,
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const group = { name };
|
|
145
|
-
if (repStr) {
|
|
146
|
-
const repetition = Number.parseInt(repStr, 10);
|
|
147
|
-
if (repetition < 1) throw new Error(`Group repetition must be ≥1, got: ${repStr} in "${path}"`);
|
|
148
|
-
group.repetition = repetition;
|
|
149
|
-
}
|
|
150
|
-
groups.push(group);
|
|
151
|
-
}
|
|
152
|
-
if (groups.length > 0) parts.groups = groups;
|
|
153
|
-
}
|
|
154
|
-
if (fieldStr) {
|
|
155
|
-
const field = Number.parseInt(fieldStr, 10);
|
|
156
|
-
if (field < 1) throw new Error(`Field number must be ≥1, got: ${fieldStr}`);
|
|
157
|
-
parts.field = field;
|
|
158
|
-
}
|
|
159
|
-
if (repetitionStr) {
|
|
160
|
-
const repetition = Number.parseInt(repetitionStr, 10);
|
|
161
|
-
if (repetition < 1) throw new Error(`Repetition number must be ≥1, got: ${repetitionStr}`);
|
|
162
|
-
parts.repetition = repetition;
|
|
163
|
-
}
|
|
164
|
-
if (componentStr) {
|
|
165
|
-
const component = Number.parseInt(componentStr, 10);
|
|
166
|
-
if (component < 1) throw new Error(`Component number must be ≥1, got: ${componentStr}`);
|
|
167
|
-
parts.component = component;
|
|
168
|
-
}
|
|
169
|
-
if (subcomponentStr) {
|
|
170
|
-
const subcomponent = Number.parseInt(subcomponentStr, 10);
|
|
171
|
-
if (subcomponent < 1) throw new Error(`Subcomponent number must be ≥1, got: ${subcomponentStr}`);
|
|
172
|
-
parts.subcomponent = subcomponent;
|
|
173
|
-
}
|
|
128
|
+
if (!match) throw new QueryError("INVALID_PATH", path);
|
|
129
|
+
const [, navigation = "", field, repetition, component, subcomponent] = match;
|
|
130
|
+
const steps = [];
|
|
131
|
+
for (const [, name, position] of navigation.matchAll(NAVIGATION_STEP_REGEX)) steps.push(position === void 0 ? { name } : {
|
|
132
|
+
name,
|
|
133
|
+
repetition: Number(position)
|
|
134
|
+
});
|
|
135
|
+
const parts = {};
|
|
136
|
+
const segment = steps.pop();
|
|
137
|
+
if (segment !== void 0) parts.segment = segment;
|
|
138
|
+
if (steps.length > 0) parts.groups = steps;
|
|
139
|
+
if (field !== void 0) parts.field = Number(field);
|
|
140
|
+
if (repetition !== void 0) parts.repetition = Number(repetition);
|
|
141
|
+
if (component !== void 0) parts.component = Number(component);
|
|
142
|
+
if (subcomponent !== void 0) parts.subcomponent = Number(subcomponent);
|
|
174
143
|
return parts;
|
|
175
144
|
}
|
|
176
145
|
//#endregion
|
|
177
|
-
//#region src/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
146
|
+
//#region src/resolve.ts
|
|
147
|
+
/** The name a path gives a node: a segment's name, or a group's ID. */
|
|
148
|
+
const keyOf = (node) => {
|
|
149
|
+
switch (node.type) {
|
|
150
|
+
case "root":
|
|
151
|
+
case "field":
|
|
152
|
+
case "field-repetition":
|
|
153
|
+
case "component":
|
|
154
|
+
case "subcomponent": return;
|
|
155
|
+
case "segment": return node.name;
|
|
156
|
+
case "group": return node.id;
|
|
188
157
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const scope = followGroups(root, parts.groups ?? [], ancestors);
|
|
199
|
-
if (!scope) return null;
|
|
200
|
-
const matchedSegments = collectSegments(scope, parts.segment.name, ancestors);
|
|
201
|
-
if (matchedSegments.length === 0) return null;
|
|
202
|
-
const match = matchedSegments[(parts.segment.repetition ?? 1) - 1];
|
|
203
|
-
if (!match) return null;
|
|
204
|
-
return [match.segment, match.ancestors];
|
|
205
|
-
}
|
|
206
|
-
function followGroups(root, groups, ancestorChain) {
|
|
207
|
-
let children = filterSegmentsAndGroups(root.children);
|
|
208
|
-
for (const locator of groups) {
|
|
209
|
-
const matchedGroups = children.filter((node) => node.type === "group" && node.id === locator.name);
|
|
210
|
-
if (matchedGroups.length === 0) return null;
|
|
211
|
-
const selected = matchedGroups[(locator.repetition ?? 1) - 1];
|
|
212
|
-
if (!selected) return null;
|
|
213
|
-
ancestorChain.push(selected);
|
|
214
|
-
children = filterSegmentsAndGroups(selected.children);
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* The `position`th of `children` (1-based) when given; otherwise every one
|
|
161
|
+
* when `every` is set, or the first.
|
|
162
|
+
*/
|
|
163
|
+
function pick(children, position, every) {
|
|
164
|
+
if (position !== void 0) {
|
|
165
|
+
const child = children[position - 1];
|
|
166
|
+
return child === void 0 ? [] : [child];
|
|
215
167
|
}
|
|
216
|
-
return children;
|
|
168
|
+
return every ? children : children.slice(0, 1);
|
|
217
169
|
}
|
|
218
170
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
171
|
+
* The children of `parent` a path step names `name`, segments and groups
|
|
172
|
+
* together: the `position`th (1-based) when given; otherwise every one when
|
|
173
|
+
* `every` is set, or the first.
|
|
221
174
|
*/
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const matchingGroups = filterSegmentsAndGroups(root.children).filter((node) => node.type === "group" && node.id === firstLocator.name);
|
|
235
|
-
if (firstLocator.repetition !== void 0) {
|
|
236
|
-
const selected = matchingGroups[firstLocator.repetition - 1];
|
|
237
|
-
if (!selected) return [];
|
|
238
|
-
const ancestors = [root, selected];
|
|
239
|
-
return collectAllScopesRecursive(selected.children, remainingGroups, ancestors);
|
|
240
|
-
}
|
|
241
|
-
for (const group of matchingGroups) {
|
|
242
|
-
const ancestors = [root, group];
|
|
243
|
-
const subScopes = collectAllScopesRecursive(group.children, remainingGroups, ancestors);
|
|
244
|
-
results.push(...subScopes);
|
|
175
|
+
function stepChildren(parent, name, position, every) {
|
|
176
|
+
const result = [];
|
|
177
|
+
const target = position ?? 1;
|
|
178
|
+
let count = 0;
|
|
179
|
+
for (const child of parent.children) {
|
|
180
|
+
if (keyOf(child) !== name) continue;
|
|
181
|
+
if (every && position === void 0) {
|
|
182
|
+
result.push(child);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
count += 1;
|
|
186
|
+
if (count === target) return [child];
|
|
245
187
|
}
|
|
246
|
-
return
|
|
188
|
+
return result;
|
|
247
189
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (!firstLocator) return [{
|
|
257
|
-
ancestors,
|
|
258
|
-
scope: filterSegmentsAndGroups(children)
|
|
259
|
-
}];
|
|
260
|
-
const matchingGroups = filterSegmentsAndGroups(children).filter((node) => node.type === "group" && node.id === firstLocator.name);
|
|
261
|
-
if (firstLocator.repetition !== void 0) {
|
|
262
|
-
const selected = matchingGroups[firstLocator.repetition - 1];
|
|
263
|
-
if (!selected) return [];
|
|
264
|
-
const newAncestors = [...ancestors, selected];
|
|
265
|
-
return collectAllScopesRecursive(selected.children, remainingGroups, newAncestors);
|
|
266
|
-
}
|
|
267
|
-
for (const group of matchingGroups) {
|
|
268
|
-
const newAncestors = [...ancestors, group];
|
|
269
|
-
const subScopes = collectAllScopesRecursive(group.children, remainingGroups, newAncestors);
|
|
270
|
-
results.push(...subScopes);
|
|
271
|
-
}
|
|
272
|
-
return results;
|
|
190
|
+
/** The children of each scope that `step` names, with their ancestors. */
|
|
191
|
+
function advance(scopes, step, every) {
|
|
192
|
+
const next = [];
|
|
193
|
+
for (const scope of scopes) for (const node of stepChildren(scope.node, step.name, step.repetition, every)) next.push({
|
|
194
|
+
ancestors: [...scope.ancestors, scope.node],
|
|
195
|
+
node
|
|
196
|
+
});
|
|
197
|
+
return next;
|
|
273
198
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
199
|
+
/**
|
|
200
|
+
* The field, repetitions, components, or subcomponents `parts` addresses in
|
|
201
|
+
* `segment`.
|
|
202
|
+
*/
|
|
203
|
+
function resolveField(segment, ancestors, fieldNumber, parts, every) {
|
|
204
|
+
const field = segment.children[fieldNumber - 1];
|
|
205
|
+
if (!field) return [];
|
|
206
|
+
const fieldAncestors = [...ancestors, segment];
|
|
207
|
+
if (parts.repetition === void 0 && parts.component === void 0) return [{
|
|
208
|
+
ancestors: fieldAncestors,
|
|
209
|
+
node: field
|
|
210
|
+
}];
|
|
211
|
+
const matches = [];
|
|
212
|
+
for (const repetition of pick(field.children ?? [], parts.repetition, every)) {
|
|
213
|
+
const repetitionAncestors = [...fieldAncestors, field];
|
|
214
|
+
if (parts.component === void 0) {
|
|
215
|
+
matches.push({
|
|
216
|
+
ancestors: repetitionAncestors,
|
|
217
|
+
node: repetition
|
|
281
218
|
});
|
|
219
|
+
continue;
|
|
282
220
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
221
|
+
const component = repetition.children?.[parts.component - 1];
|
|
222
|
+
if (!component) continue;
|
|
223
|
+
const componentAncestors = [...repetitionAncestors, repetition];
|
|
224
|
+
if (parts.subcomponent === void 0) {
|
|
225
|
+
matches.push({
|
|
226
|
+
ancestors: componentAncestors,
|
|
227
|
+
node: component
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
286
230
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const result = [];
|
|
292
|
-
for (const node of nodes) {
|
|
293
|
-
if (node.type === "group" && node.id === targetGroupId) result.push({
|
|
294
|
-
ancestors: [...ancestors],
|
|
295
|
-
group: node
|
|
231
|
+
const subcomponent = component.children?.[parts.subcomponent - 1];
|
|
232
|
+
if (subcomponent) matches.push({
|
|
233
|
+
ancestors: [...componentAncestors, component],
|
|
234
|
+
node: subcomponent
|
|
296
235
|
});
|
|
297
|
-
if (node.type === "group") {
|
|
298
|
-
const nextAncestors = [...ancestors, node];
|
|
299
|
-
result.push(...collectGroups(filterSegmentsAndGroups(node.children), targetGroupId, nextAncestors));
|
|
300
|
-
}
|
|
301
236
|
}
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
function locateField(segment, fieldNum, parentAncestors) {
|
|
305
|
-
const field = getFields(segment)[fieldNum - 1];
|
|
306
|
-
if (!field) return null;
|
|
307
|
-
return [field, [...parentAncestors, segment]];
|
|
308
|
-
}
|
|
309
|
-
function locateRepetition(field, repetition, parentAncestors) {
|
|
310
|
-
if (repetition < 1) return null;
|
|
311
|
-
const node = field.children?.[repetition - 1];
|
|
312
|
-
if (!node) return null;
|
|
313
|
-
return [node, [...parentAncestors, field]];
|
|
314
|
-
}
|
|
315
|
-
function locateComponent(repetition, componentNum, parentAncestors) {
|
|
316
|
-
if (componentNum < 1) return null;
|
|
317
|
-
const component = repetition.children?.[componentNum - 1];
|
|
318
|
-
if (!component) return null;
|
|
319
|
-
return [component, [...parentAncestors, repetition]];
|
|
320
|
-
}
|
|
321
|
-
function locateSubcomponent(component, subcomponentNum, parentAncestors) {
|
|
322
|
-
if (subcomponentNum < 1) return null;
|
|
323
|
-
const node = component.children?.[subcomponentNum - 1];
|
|
324
|
-
if (!node) return null;
|
|
325
|
-
return [node, [...parentAncestors, component]];
|
|
237
|
+
return matches;
|
|
326
238
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
239
|
+
/**
|
|
240
|
+
* The nodes `parts` addresses under `start`, in document order, each with its
|
|
241
|
+
* own ancestors array, which begins at `start`.
|
|
242
|
+
*
|
|
243
|
+
* Every step names a direct child of the node before it; the first step
|
|
244
|
+
* names a direct child of `start`. A step's `[n]` counts the children with
|
|
245
|
+
* that name, segments and groups together. A step without `[n]` matches the
|
|
246
|
+
* first such child, or every one when `every` is set; so does a field
|
|
247
|
+
* without a repetition when the path names a component.
|
|
248
|
+
*/
|
|
249
|
+
function resolve(start, parts, every) {
|
|
250
|
+
let scopes = [{
|
|
251
|
+
ancestors: [],
|
|
252
|
+
node: start
|
|
253
|
+
}];
|
|
254
|
+
for (const group of parts.groups ?? []) scopes = advance(scopes, group, every);
|
|
255
|
+
if (parts.segment !== void 0) scopes = advance(scopes, parts.segment, every);
|
|
256
|
+
if (parts.field === void 0) return scopes;
|
|
257
|
+
const matches = [];
|
|
258
|
+
for (const { node, ancestors } of scopes) {
|
|
259
|
+
if (node.type !== "segment") continue;
|
|
260
|
+
for (const match of resolveField(node, ancestors, parts.field, parts, every)) matches.push(match);
|
|
261
|
+
}
|
|
262
|
+
return matches;
|
|
335
263
|
}
|
|
336
264
|
//#endregion
|
|
337
265
|
//#region src/select.ts
|
|
338
266
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
267
|
+
* The node `path` addresses under `node`, with its ancestors from `node`, or
|
|
268
|
+
* `null`.
|
|
341
269
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
270
|
+
* Every step of `path` names a direct child of the node before it; the first
|
|
271
|
+
* step names a direct child of `node`, so a path read from a segment starts
|
|
272
|
+
* with the field. A step without `[n]` names the first such child, so
|
|
273
|
+
* `select` returns `null` when that child does not lead to a match, even if a
|
|
274
|
+
* later one does; `selectAll` returns every match.
|
|
345
275
|
*
|
|
346
276
|
* @example
|
|
347
277
|
* ```typescript
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* result.ancestors; // [Root, Segment]
|
|
352
|
-
* }
|
|
278
|
+
* select(root, "PID.5"); // { node: Field, ancestors: [Root, Segment] }
|
|
279
|
+
* select(root, "PATIENT_RESULT.PATIENT.PID.5");
|
|
280
|
+
* select(segment, "5.1"); // { node: Component, ancestors: [Segment, Field, FieldRepetition] }
|
|
353
281
|
* ```;
|
|
354
282
|
*
|
|
355
|
-
* @param
|
|
356
|
-
* @param path - The
|
|
357
|
-
* @returns
|
|
283
|
+
* @param node - The message, group, or segment to read from.
|
|
284
|
+
* @param path - The path to the node.
|
|
285
|
+
* @returns The node and its ancestors, or `null` when nothing matches.
|
|
358
286
|
*/
|
|
359
|
-
function select(
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
const result = locateSegmentOrGroup(root, parts);
|
|
363
|
-
if (!result) return null;
|
|
364
|
-
return {
|
|
365
|
-
ancestors: result[1],
|
|
366
|
-
node: result[0]
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
const segmentResult = locateSegment(root, parts);
|
|
370
|
-
if (!segmentResult) return null;
|
|
371
|
-
const [segment, segmentAncestors] = segmentResult;
|
|
372
|
-
const fieldResult = locateField(segment, parts.field, segmentAncestors);
|
|
373
|
-
if (!fieldResult) return null;
|
|
374
|
-
const [field, fieldAncestors] = fieldResult;
|
|
375
|
-
if (parts.component === void 0 && parts.subcomponent === void 0 && parts.repetition === void 0) return {
|
|
376
|
-
ancestors: fieldAncestors,
|
|
377
|
-
node: field
|
|
378
|
-
};
|
|
379
|
-
const repetitionResult = locateRepetition(field, parts.repetition ?? 1, fieldAncestors);
|
|
380
|
-
if (!repetitionResult) return null;
|
|
381
|
-
const [repetitionNode, repetitionAncestors] = repetitionResult;
|
|
382
|
-
if (parts.component === void 0) return {
|
|
383
|
-
ancestors: repetitionAncestors,
|
|
384
|
-
node: repetitionNode
|
|
385
|
-
};
|
|
386
|
-
const componentResult = locateComponent(repetitionNode, parts.component, repetitionAncestors);
|
|
387
|
-
if (!componentResult) return null;
|
|
388
|
-
const [componentNode, componentAncestors] = componentResult;
|
|
389
|
-
if (parts.subcomponent === void 0) return {
|
|
390
|
-
ancestors: componentAncestors,
|
|
391
|
-
node: componentNode
|
|
392
|
-
};
|
|
393
|
-
const subResult = locateSubcomponent(componentNode, parts.subcomponent, componentAncestors);
|
|
394
|
-
if (!subResult) return null;
|
|
395
|
-
return {
|
|
396
|
-
ancestors: subResult[1],
|
|
397
|
-
node: subResult[0]
|
|
398
|
-
};
|
|
287
|
+
function select(node, path) {
|
|
288
|
+
const [match] = resolve(node, parse(path), false);
|
|
289
|
+
return match ?? null;
|
|
399
290
|
}
|
|
400
291
|
/**
|
|
401
|
-
*
|
|
292
|
+
* Every node `path` addresses under `node`, with its ancestors from `node`, in
|
|
293
|
+
* document order.
|
|
402
294
|
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
295
|
+
* Every step of `path` names a direct child of the node before it; the first
|
|
296
|
+
* step names a direct child of `node`, so a path read from a segment starts
|
|
297
|
+
* with the field. A step without `[n]` matches every such child, and so does
|
|
298
|
+
* a field without a repetition when `path` names a component.
|
|
406
299
|
*
|
|
407
300
|
* @example
|
|
408
301
|
* ```typescript
|
|
409
|
-
* //
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
* console.log(node.type); // 'segment'
|
|
413
|
-
* }
|
|
414
|
-
*
|
|
415
|
-
* // Get all patient name fields
|
|
416
|
-
* const names = selectAll(root, "PID-5");
|
|
302
|
+
* selectAll(root, "OBX.5"); // OBX-5 of every top-level OBX
|
|
303
|
+
* selectAll(root, "OBX.1[2]"); // the second repetition of OBX-1 in every OBX
|
|
304
|
+
* selectAll(segment, "3.1"); // the first component of every repetition of field 3
|
|
417
305
|
* ```;
|
|
418
306
|
*
|
|
419
|
-
* @param
|
|
420
|
-
* @param path - The
|
|
421
|
-
* @returns
|
|
307
|
+
* @param node - The message, group, or segment to read from.
|
|
308
|
+
* @param path - The path to the nodes.
|
|
309
|
+
* @returns The nodes and their ancestors; empty when nothing matches.
|
|
422
310
|
*/
|
|
423
|
-
function selectAll(
|
|
424
|
-
|
|
425
|
-
if (parts.segment.repetition !== void 0 || parts.repetition !== void 0) {
|
|
426
|
-
const result = select(root, path);
|
|
427
|
-
return result ? [result] : [];
|
|
428
|
-
}
|
|
429
|
-
const results = [];
|
|
430
|
-
const scopes = collectAllScopes(root, parts.groups ?? []);
|
|
431
|
-
const segments = [];
|
|
432
|
-
const includeGroups = parts.field === void 0;
|
|
433
|
-
const groups = [];
|
|
434
|
-
for (const { scope, ancestors } of scopes) {
|
|
435
|
-
const matchingSegments = collectSegments(scope, parts.segment.name, ancestors);
|
|
436
|
-
segments.push(...matchingSegments);
|
|
437
|
-
if (includeGroups) {
|
|
438
|
-
const matchingGroups = collectGroups(scope, parts.segment.name, ancestors);
|
|
439
|
-
for (const { group, ancestors: groupAncestors } of matchingGroups) groups.push({
|
|
440
|
-
ancestors: groupAncestors,
|
|
441
|
-
group
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (parts.field === void 0) {
|
|
446
|
-
for (const { segment, ancestors } of segments) results.push({
|
|
447
|
-
ancestors,
|
|
448
|
-
node: segment
|
|
449
|
-
});
|
|
450
|
-
for (const { group, ancestors } of groups) results.push({
|
|
451
|
-
ancestors,
|
|
452
|
-
node: group
|
|
453
|
-
});
|
|
454
|
-
return results;
|
|
455
|
-
}
|
|
456
|
-
for (const { segment, ancestors: segmentAncestors } of segments) {
|
|
457
|
-
const fieldResult = locateField(segment, parts.field, segmentAncestors);
|
|
458
|
-
if (!fieldResult) continue;
|
|
459
|
-
const [field, fieldAncestors] = fieldResult;
|
|
460
|
-
if (parts.component === void 0 && parts.subcomponent === void 0 && parts.repetition === void 0) {
|
|
461
|
-
results.push({
|
|
462
|
-
ancestors: fieldAncestors,
|
|
463
|
-
node: field
|
|
464
|
-
});
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
if (!field.children) continue;
|
|
468
|
-
for (const repetitionNode of field.children) {
|
|
469
|
-
const repetitionAncestors = [...fieldAncestors, field];
|
|
470
|
-
if (parts.component === void 0) {
|
|
471
|
-
results.push({
|
|
472
|
-
ancestors: repetitionAncestors,
|
|
473
|
-
node: repetitionNode
|
|
474
|
-
});
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
const componentResult = locateComponent(repetitionNode, parts.component, repetitionAncestors);
|
|
478
|
-
if (!componentResult) continue;
|
|
479
|
-
const [componentNode, componentAncestors] = componentResult;
|
|
480
|
-
if (parts.subcomponent === void 0) {
|
|
481
|
-
results.push({
|
|
482
|
-
ancestors: componentAncestors,
|
|
483
|
-
node: componentNode
|
|
484
|
-
});
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
const subResult = locateSubcomponent(componentNode, parts.subcomponent, componentAncestors);
|
|
488
|
-
if (subResult) results.push({
|
|
489
|
-
ancestors: subResult[1],
|
|
490
|
-
node: subResult[0]
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return results;
|
|
311
|
+
function selectAll(node, path) {
|
|
312
|
+
return resolve(node, parse(path), true);
|
|
495
313
|
}
|
|
496
314
|
//#endregion
|
|
497
315
|
//#region src/value.ts
|
|
@@ -502,7 +320,7 @@ function selectAll(root, path) {
|
|
|
502
320
|
* This is a convenience function that automatically traverses through container
|
|
503
321
|
* nodes (Field → FieldRepetition → Component → Subcomponent) to extract the
|
|
504
322
|
* primary string value. When a container has multiple children, the first child
|
|
505
|
-
* is selected (e.g., `value(tree, "MSH
|
|
323
|
+
* is selected (e.g., `value(tree, "MSH.12")` returns the first component of a
|
|
506
324
|
* composite VID field).
|
|
507
325
|
*
|
|
508
326
|
* Nodes with no children return `value: null` to differentiate between empty
|
|
@@ -510,42 +328,51 @@ function selectAll(root, path) {
|
|
|
510
328
|
*
|
|
511
329
|
* @example
|
|
512
330
|
* ```typescript
|
|
513
|
-
* const result = value(root, "PID
|
|
331
|
+
* const result = value(root, "PID.5.1");
|
|
514
332
|
* if (result) {
|
|
515
333
|
* console.log(result.value); // string | null
|
|
516
334
|
* console.log(result.node.type); // 'subcomponent'
|
|
517
335
|
* console.log(result.ancestors); // [Root, Segment, Field, ...]
|
|
518
336
|
* }
|
|
337
|
+
*
|
|
338
|
+
* value(segment, "5.1"); // the same value, read from the PID segment
|
|
519
339
|
* ```;
|
|
520
340
|
*
|
|
521
|
-
* @param
|
|
341
|
+
* @param node - The message, group, or segment to read from
|
|
522
342
|
* @param path - The HL7 path string
|
|
523
|
-
* @returns Object containing the value, node, and ancestors
|
|
524
|
-
* found
|
|
343
|
+
* @returns Object containing the value, node, and ancestors from `node`, or
|
|
344
|
+
* null if not found
|
|
525
345
|
*/
|
|
526
|
-
function value(
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
346
|
+
function value(node, path) {
|
|
347
|
+
const selected = select(node, path);
|
|
348
|
+
return selected ? drill(selected.node, selected.ancestors) : null;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* The first subcomponent under `node`, with its value, or `node` with value
|
|
352
|
+
* `null` when a field, repetition, or component on the way has no children.
|
|
353
|
+
* `null` for a root, group, or segment.
|
|
354
|
+
*/
|
|
355
|
+
function drill(node, ancestors) {
|
|
356
|
+
switch (node.type) {
|
|
357
|
+
case "root":
|
|
358
|
+
case "group":
|
|
359
|
+
case "segment": return null;
|
|
360
|
+
case "subcomponent": return {
|
|
361
|
+
ancestors,
|
|
536
362
|
node,
|
|
537
|
-
value:
|
|
363
|
+
value: node.value
|
|
538
364
|
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
365
|
+
case "field":
|
|
366
|
+
case "field-repetition":
|
|
367
|
+
case "component": {
|
|
368
|
+
const first = node.children?.[0];
|
|
369
|
+
return first ? drill(first, [...ancestors, node]) : {
|
|
370
|
+
ancestors,
|
|
371
|
+
node,
|
|
372
|
+
value: null
|
|
373
|
+
};
|
|
374
|
+
}
|
|
543
375
|
}
|
|
544
|
-
return {
|
|
545
|
-
ancestors,
|
|
546
|
-
node,
|
|
547
|
-
value: node.value
|
|
548
|
-
};
|
|
549
376
|
}
|
|
550
377
|
//#endregion
|
|
551
378
|
//#region src/delimiters.ts
|
|
@@ -570,9 +397,9 @@ const MSH2_KEYS = [
|
|
|
570
397
|
*/
|
|
571
398
|
function delimiters(root) {
|
|
572
399
|
const result = { ...DEFAULT_DELIMITERS };
|
|
573
|
-
const msh1 = value(root, "MSH
|
|
400
|
+
const msh1 = value(root, "MSH.1")?.value;
|
|
574
401
|
if (msh1 && msh1.length === 1) result.field = msh1;
|
|
575
|
-
const msh2 = value(root, "MSH
|
|
402
|
+
const msh2 = value(root, "MSH.2")?.value;
|
|
576
403
|
if (msh2) {
|
|
577
404
|
const chars = [...msh2];
|
|
578
405
|
for (const [i, key] of MSH2_KEYS.entries()) {
|
|
@@ -584,204 +411,99 @@ function delimiters(root) {
|
|
|
584
411
|
}
|
|
585
412
|
//#endregion
|
|
586
413
|
//#region src/format.ts
|
|
587
|
-
/** What a path names a node by: a segment's name, or a group's ID. */
|
|
588
|
-
const keyOf = (node) => node.type === "group" ? node.id : node.name;
|
|
589
414
|
/**
|
|
590
415
|
* Format a node and its ancestor chain into a canonical HL7v2 path string.
|
|
591
416
|
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
417
|
+
* When `ancestors` starts at the root, `select(root, path)` returns `node`:
|
|
418
|
+
* the path names every group, and `[n]` counts the siblings with the same
|
|
419
|
+
* name, segments and groups together.
|
|
595
420
|
*
|
|
596
421
|
* @example
|
|
597
422
|
* ```typescript
|
|
598
423
|
* visit(tree, "field", (node, ancestors) => {
|
|
599
|
-
* format(node, ancestors); // "PID
|
|
424
|
+
* format(node, ancestors); // "PID.5"
|
|
600
425
|
* });
|
|
601
426
|
*
|
|
602
|
-
* const result = select(root, "PID
|
|
603
|
-
* format(result.node, result.ancestors); // "PID
|
|
427
|
+
* const result = select(root, "PID.5.2");
|
|
428
|
+
* format(result.node, result.ancestors); // "PID.5.2"
|
|
604
429
|
* ```;
|
|
605
430
|
*
|
|
606
431
|
* @param node - The target AST node
|
|
607
432
|
* @param ancestors - Ancestor chain from root to parent (as provided by
|
|
608
433
|
* visit/select)
|
|
609
|
-
* @returns Canonical path string (e.g., "PID
|
|
610
|
-
* ancestor chain contains no segment or group
|
|
434
|
+
* @returns Canonical path string (e.g., "PID.5.2", "ORDER.ORC.1"), or `null` if the
|
|
435
|
+
* ancestor chain contains no segment or group, or an ancestor does not
|
|
436
|
+
* hold the next node in the chain
|
|
611
437
|
*/
|
|
612
438
|
function format(node, ancestors) {
|
|
613
|
-
let result = null;
|
|
614
439
|
const chain = [...ancestors, node];
|
|
615
|
-
|
|
616
|
-
|
|
440
|
+
const steps = [];
|
|
441
|
+
let positions = "";
|
|
442
|
+
for (const [index, current] of chain.entries()) {
|
|
443
|
+
const position = positionOf(current, chain[index - 1]);
|
|
444
|
+
if (position === 0) return null;
|
|
617
445
|
switch (current.type) {
|
|
618
446
|
case "root": break;
|
|
619
447
|
case "group":
|
|
620
|
-
case "segment":
|
|
621
|
-
|
|
622
|
-
result = (result ?? "") + keyOf(current);
|
|
623
|
-
if (pos > 1) result += `[${pos}]`;
|
|
624
|
-
if (current.type === "group" && current !== node) result += "-";
|
|
448
|
+
case "segment":
|
|
449
|
+
steps.push(position > 1 ? `${keyOf(current)}[${position}]` : `${keyOf(current)}`);
|
|
625
450
|
break;
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
result += `-${positionOf(current, parent)}`;
|
|
629
|
-
break;
|
|
630
|
-
case "field-repetition": {
|
|
631
|
-
const pos = positionOf(current, parent);
|
|
632
|
-
if (pos > 1 || current === node) result += `[${pos}]`;
|
|
451
|
+
case "field-repetition":
|
|
452
|
+
if (position > 1 || current === node) positions += `[${position}]`;
|
|
633
453
|
break;
|
|
634
|
-
|
|
454
|
+
case "field":
|
|
635
455
|
case "component":
|
|
636
|
-
result += `.${positionOf(current, parent)}`;
|
|
637
|
-
break;
|
|
638
456
|
case "subcomponent":
|
|
639
|
-
|
|
457
|
+
positions += `.${position}`;
|
|
640
458
|
break;
|
|
641
459
|
}
|
|
642
460
|
}
|
|
643
|
-
return
|
|
461
|
+
return steps.length === 0 ? null : steps.join(".") + positions;
|
|
644
462
|
}
|
|
645
463
|
/**
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
* group's ID): the 2nd PID in [MSH, PID, OBX, PID] is PID[2], not PID[4].
|
|
650
|
-
*
|
|
651
|
-
* For positional nodes (fields, repetitions, components, subcomponents),
|
|
652
|
-
* uses the array index directly.
|
|
464
|
+
* The 1-based position of `node` among the children of `parent`: for a
|
|
465
|
+
* segment or group, its `[n]` counted as `select` counts it; otherwise its
|
|
466
|
+
* index. 1 without a parent; 0 when `parent` does not hold `node`.
|
|
653
467
|
*/
|
|
654
468
|
function positionOf(node, parent) {
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
throw new Error("format(): node not found in parent's children array");
|
|
469
|
+
if (parent === void 0 || !("children" in parent)) return 1;
|
|
470
|
+
switch (node.type) {
|
|
471
|
+
case "group":
|
|
472
|
+
case "segment": return stepChildren(parent, keyOf(node), void 0, true).indexOf(node) + 1;
|
|
473
|
+
case "root":
|
|
474
|
+
case "field":
|
|
475
|
+
case "field-repetition":
|
|
476
|
+
case "component":
|
|
477
|
+
case "subcomponent": return parent.children.indexOf(node) + 1;
|
|
665
478
|
}
|
|
666
|
-
const idx = children.indexOf(node);
|
|
667
|
-
if (idx === -1) throw new Error("format(): node not found in parent's children array");
|
|
668
|
-
return idx + 1;
|
|
669
479
|
}
|
|
670
480
|
//#endregion
|
|
671
481
|
//#region src/matches.ts
|
|
672
482
|
/**
|
|
673
|
-
*
|
|
483
|
+
* Whether `select(node, path)` finds a node.
|
|
674
484
|
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
485
|
+
* A step without `[n]` names the first such child, so `matches` is `false`
|
|
486
|
+
* when that child does not lead to a match, even if a later one does.
|
|
677
487
|
*
|
|
678
488
|
* @example
|
|
679
489
|
* ```typescript
|
|
680
|
-
* if (matches(root, "PID
|
|
490
|
+
* if (matches(root, "PID.5")) {
|
|
681
491
|
* console.log("Patient has a name field");
|
|
682
492
|
* }
|
|
683
493
|
*
|
|
684
|
-
* if (!matches(root, "OBX
|
|
494
|
+
* if (!matches(root, "OBX.5")) {
|
|
685
495
|
* throw new Error("Missing observation value");
|
|
686
496
|
* }
|
|
687
497
|
* ```;
|
|
688
498
|
*
|
|
689
|
-
* @param
|
|
499
|
+
* @param node - The message, group, or segment to read from
|
|
690
500
|
* @param path - The HL7 path string
|
|
691
501
|
* @returns True if the path resolves to a node, false otherwise
|
|
692
502
|
*/
|
|
693
|
-
function matches(
|
|
694
|
-
return select(
|
|
695
|
-
}
|
|
696
|
-
//#endregion
|
|
697
|
-
//#region src/set.ts
|
|
698
|
-
/**
|
|
699
|
-
* Set a string value at the given HL7 path, creating all missing intermediate
|
|
700
|
-
* nodes as needed (fields, repetitions, components, subcomponents).
|
|
701
|
-
*
|
|
702
|
-
* This is the write counterpart to {@link value}. It handles the full HL7v2
|
|
703
|
-
* path syntax including group navigation and segment repetitions.
|
|
704
|
-
*
|
|
705
|
-
* Padding nodes are created with empty children arrays — they represent
|
|
706
|
-
* structurally absent nodes, not empty values. Only the target node gets
|
|
707
|
-
* a fully populated chain down to the subcomponent.
|
|
708
|
-
*
|
|
709
|
-
* @example
|
|
710
|
-
* ```typescript
|
|
711
|
-
* // Update existing value
|
|
712
|
-
* set(tree, "MSH-12", "2.7.1");
|
|
713
|
-
*
|
|
714
|
-
* // Create missing MSH-9.3 (fills gaps with empty components)
|
|
715
|
-
* set(tree, "MSH-9.3", "ADT_A01");
|
|
716
|
-
*
|
|
717
|
-
* // Set a specific repetition
|
|
718
|
-
* set(tree, "PID-3[2]", "ID-002");
|
|
719
|
-
*
|
|
720
|
-
* // Set a subcomponent
|
|
721
|
-
* set(tree, "PID-5.1.2", "suffix");
|
|
722
|
-
* ```;
|
|
723
|
-
*
|
|
724
|
-
* @param root - The root node to modify
|
|
725
|
-
* @param path - The HL7 path string (must include at least a field)
|
|
726
|
-
* @param val - The string value to set
|
|
727
|
-
* @throws When the path has no field specifier (segment-only paths)
|
|
728
|
-
* @throws When the target segment does not exist in the tree
|
|
729
|
-
*/
|
|
730
|
-
function set(root, path, val) {
|
|
731
|
-
const parts = parse(path);
|
|
732
|
-
if (parts.field === void 0) throw new Error(`Cannot set value on segment-only path: "${path}"`);
|
|
733
|
-
const segmentResult = locateSegment(root, parts);
|
|
734
|
-
if (!segmentResult) throw new Error(`Segment "${parts.segment.name}" not found in tree`);
|
|
735
|
-
const [segment] = segmentResult;
|
|
736
|
-
const fieldIndex = parts.field - 1;
|
|
737
|
-
padArray(segment.children, fieldIndex + 1, stubField);
|
|
738
|
-
const field = segment.children[fieldIndex];
|
|
739
|
-
const repIndex = (parts.repetition ?? 1) - 1;
|
|
740
|
-
padArray(field.children, repIndex + 1, stubRepetition);
|
|
741
|
-
const repetition = field.children[repIndex];
|
|
742
|
-
const compIndex = (parts.component ?? 1) - 1;
|
|
743
|
-
padArray(repetition.children, compIndex + 1, stubComponent);
|
|
744
|
-
const component = repetition.children[compIndex];
|
|
745
|
-
const subIndex = (parts.subcomponent ?? 1) - 1;
|
|
746
|
-
padArray(component.children, subIndex + 1, stubSubcomponent);
|
|
747
|
-
component.children[subIndex].value = val;
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Pad an array to a target length using a factory function.
|
|
751
|
-
*/
|
|
752
|
-
function padArray(arr, targetLength, factory) {
|
|
753
|
-
while (arr.length < targetLength) arr.push(factory());
|
|
754
|
-
}
|
|
755
|
-
/**
|
|
756
|
-
* Stub nodes for padding — created with empty children arrays to represent
|
|
757
|
-
* structurally absent nodes. These are distinguishable from nodes with empty
|
|
758
|
-
* values (which have a subcomponent with value "").
|
|
759
|
-
*/
|
|
760
|
-
function stubField() {
|
|
761
|
-
return {
|
|
762
|
-
children: [],
|
|
763
|
-
type: "field"
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
function stubRepetition() {
|
|
767
|
-
return {
|
|
768
|
-
children: [],
|
|
769
|
-
type: "field-repetition"
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
function stubComponent() {
|
|
773
|
-
return {
|
|
774
|
-
children: [],
|
|
775
|
-
type: "component"
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
function stubSubcomponent() {
|
|
779
|
-
return {
|
|
780
|
-
type: "subcomponent",
|
|
781
|
-
value: ""
|
|
782
|
-
};
|
|
503
|
+
function matches(node, path) {
|
|
504
|
+
return select(node, path) !== null;
|
|
783
505
|
}
|
|
784
506
|
//#endregion
|
|
785
|
-
export { delimiters, format, matches, parse, select, selectAll,
|
|
507
|
+
export { QueryError, QueryErrorCode, delimiters, format, matches, parse, select, selectAll, value };
|
|
786
508
|
|
|
787
509
|
//# sourceMappingURL=index.js.map
|