@cosyte/astm 0.0.15 → 0.0.18
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/CHANGELOG.md +290 -0
- package/README.md +118 -11
- package/dist/index.cjs +413 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +965 -58
- package/dist/index.d.ts +965 -58
- package/dist/index.mjs +407 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,19 @@ var FATAL_CODES = {
|
|
|
4
4
|
EMPTY_INPUT: "EMPTY_INPUT",
|
|
5
5
|
/** The first record is not an `H` (header) record: an ASTM message must lead with `H`. */
|
|
6
6
|
ASTM_RECORD_NO_HEADER: "ASTM_RECORD_NO_HEADER",
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The first `H` record could not declare all four delimiters (field/repeat/component/escape).
|
|
9
|
+
* One code, four reasons, and the message says which: the record is not a header, it is shorter
|
|
10
|
+
* than a header plus a three-character definition, its definition field holds fewer than three
|
|
11
|
+
* characters before the next field separator, or its field separator is also one of the other
|
|
12
|
+
* three. Only the second of those is "too short", so read the message rather than assuming it.
|
|
13
|
+
*
|
|
14
|
+
* **Two of the four cannot be reached on this fatal, and are not dead code.** A first record that
|
|
15
|
+
* is not an `H` raises `ASTM_RECORD_NO_HEADER` before the declaration is ever read, and a field
|
|
16
|
+
* separator reused among the other three ends the delimiter definition where it appears, so the
|
|
17
|
+
* truncation reason answers first. The reader names all four because it is also called directly
|
|
18
|
+
* and on later headers, where the same conditions are a warning rather than this fatal.
|
|
19
|
+
*/
|
|
8
20
|
ASTM_RECORD_UNDECLARED_DELIMITERS: "ASTM_RECORD_UNDECLARED_DELIMITERS"
|
|
9
21
|
};
|
|
10
22
|
var AstmParseError = class extends Error {
|
|
@@ -43,17 +55,26 @@ var CANONICAL_DELIMITERS = {
|
|
|
43
55
|
escape: "&"
|
|
44
56
|
};
|
|
45
57
|
function readDelimiters(headerRecord) {
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
const result = readDelimiterDeclaration(headerRecord);
|
|
59
|
+
return result.ok ? result.delimiters : void 0;
|
|
60
|
+
}
|
|
61
|
+
function readDelimiterDeclaration(headerRecord) {
|
|
62
|
+
if (headerRecord.charAt(0) !== "H") return { ok: false, fault: "not-a-header" };
|
|
63
|
+
if (headerRecord.length < 5) return { ok: false, fault: "record-too-short" };
|
|
48
64
|
const field = headerRecord.charAt(1);
|
|
49
65
|
const defEnd = headerRecord.indexOf(field, 2);
|
|
50
66
|
const definition = headerRecord.slice(2, defEnd === -1 ? headerRecord.length : defEnd);
|
|
51
|
-
if (definition.length < 3) return
|
|
67
|
+
if (definition.length < 3) return { ok: false, fault: "definition-truncated" };
|
|
52
68
|
const repeat = definition.charAt(0);
|
|
53
69
|
const component = definition.charAt(1);
|
|
54
70
|
const escape = definition.charAt(2);
|
|
55
|
-
if (field === repeat || field === component || field === escape)
|
|
56
|
-
|
|
71
|
+
if (field === repeat || field === component || field === escape) {
|
|
72
|
+
return { ok: false, fault: "field-separator-reused" };
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, delimiters: { field, repeat, component, escape } };
|
|
75
|
+
}
|
|
76
|
+
function hasCollidingRoles(d) {
|
|
77
|
+
return d.repeat === d.component || d.repeat === d.escape || d.component === d.escape;
|
|
57
78
|
}
|
|
58
79
|
function isNonStandard(d) {
|
|
59
80
|
return d.field !== CANONICAL_DELIMITERS.field || d.repeat !== CANONICAL_DELIMITERS.repeat || d.component !== CANONICAL_DELIMITERS.component || d.escape !== CANONICAL_DELIMITERS.escape;
|
|
@@ -142,18 +163,237 @@ var WARNING_CODES = {
|
|
|
142
163
|
* boundaries, silently, and this can happen to one record inside a run of these warnings); and a
|
|
143
164
|
* set differing in the repeat, component or escape role usually splits into fields normally,
|
|
144
165
|
* where a mis-split component can cost a test identity while the value survives, and where an
|
|
145
|
-
* `&X&` sequence whose body is
|
|
166
|
+
* `&X&` sequence whose body is an unrecognized character that is itself a delimiter in force is an
|
|
167
|
+
* opaque atom, so that delimiter does not split
|
|
146
168
|
* and every field after it shifts. The escape role's worst case has narrowed and not
|
|
147
169
|
* disappeared: an escape character heading no sequence is now read as a literal and reported
|
|
148
170
|
* under {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER} rather than merging the rest of the
|
|
149
|
-
* record,
|
|
150
|
-
* {@link WARNING_CODES.
|
|
171
|
+
* record, and a delimiter swallowed inside an `&X&` body now raises
|
|
172
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE} as well as the tolerable
|
|
173
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, though it still splits the same way. The
|
|
174
|
+
* mirror of that case, where the leftmost alignment lets a delimiter split that a competing
|
|
175
|
+
* alignment would have held, gains a boundary rather than losing one, so the record splits into
|
|
176
|
+
* more fields than another reading gives and this code cannot see it either
|
|
177
|
+
* ({@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT} reports that). Treat this code as a
|
|
151
178
|
* report that one record definitely lost its fields, never as a sweep that would have fired if
|
|
152
179
|
* any had.
|
|
153
180
|
*/
|
|
154
181
|
ASTM_RECORD_FIELDS_UNSEPARATED: "ASTM_RECORD_FIELDS_UNSEPARATED",
|
|
155
182
|
/** The header declared delimiters other than the canonical `H|\^&`: tolerated, noted. */
|
|
156
183
|
ASTM_NONSTANDARD_DELIMITERS: "ASTM_NONSTANDARD_DELIMITERS",
|
|
184
|
+
/**
|
|
185
|
+
* A header declared **one character in two roles**, so the boundary between those two roles is
|
|
186
|
+
* not recoverable from the bytes. The declaration is still read and honored and no record is
|
|
187
|
+
* dropped: what is gone is a distinction the sender's own bytes no longer carry.
|
|
188
|
+
*
|
|
189
|
+
* The field separator is not part of this: a declaration naming it in another role is refused
|
|
190
|
+
* earlier (the `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal on the first header,
|
|
191
|
+
* {@link WARNING_CODES.ASTM_RECORD_UNREADABLE_REDECLARATION} on a later one). What this code
|
|
192
|
+
* covers is the three unordered pairs among the rest: **repeat/component, repeat/escape,
|
|
193
|
+
* component/escape**.
|
|
194
|
+
*
|
|
195
|
+
* Measured on `H|^^&` (repeat and component both `^`): the field `A^B^C^D` reads back as four
|
|
196
|
+
* repeats of one component each, so `components` holds only `A` and a two-repeats-of-two-components
|
|
197
|
+
* reading cannot be recovered. Measured on `H|\&&` (component and escape both `&`): `A&B` splits
|
|
198
|
+
* into two components while `A&F&B` reads as the single component `A|B`, so the same character
|
|
199
|
+
* means two different things depending on what follows it.
|
|
200
|
+
*
|
|
201
|
+
* **It is not tolerable**, and the reason is the pair it travels with: such a set is always
|
|
202
|
+
* non-canonical, so before this code existed the only warning on the stream was
|
|
203
|
+
* {@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS}, which a profile may tolerate. That made a
|
|
204
|
+
* structurally unreadable declaration indistinguishable, to a strict consumer, from an ordinary
|
|
205
|
+
* vendor set. Emit refuses the same sets outright (`ASTM_EMIT_INVALID_DELIMITERS`).
|
|
206
|
+
*
|
|
207
|
+
* One warning per header that **changes** the set in force into such a set, not one per colliding
|
|
208
|
+
* pair. A later header restating the colliding set already in force is a no-op and warns nothing,
|
|
209
|
+
* on the same rule as {@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS}: the set it names was
|
|
210
|
+
* already reported when it came into force.
|
|
211
|
+
*/
|
|
212
|
+
ASTM_RECORD_DELIMITER_ROLE_COLLISION: "ASTM_RECORD_DELIMITER_ROLE_COLLISION",
|
|
213
|
+
/**
|
|
214
|
+
* An escape sequence whose body was **not** a recognized mnemonic held a character that is one of
|
|
215
|
+
* the three splitting delimiters in force, and the atom rule (an `&X&` triple is opaque) kept it
|
|
216
|
+
* out of the split, so a boundary the bytes carried never became one. The sequence is preserved
|
|
217
|
+
* verbatim in the value; nothing is dropped and nothing is re-split.
|
|
218
|
+
*
|
|
219
|
+
* Measured on the canonical set: `R|1|^^^687|28.6&|&U/L||||F` reads `value` = `28.6&|&U/L`, with
|
|
220
|
+
* **no units** and status `unspecified` rather than `final`.
|
|
221
|
+
*
|
|
222
|
+
* **This is the code that says a boundary was lost.** The same condition also raises
|
|
223
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, which stays and stays tolerable: that code
|
|
224
|
+
* reports only that a body was not recognized, which is true of bodies that cost nothing. This one
|
|
225
|
+
* is the narrower, safety-critical half, and no profile may tolerate it.
|
|
226
|
+
*
|
|
227
|
+
* **What it does not do is repair anything.** The atom rule is unchanged (it is what keeps `&F&`
|
|
228
|
+
* one token under a set that names `F` as a delimiter), so the value is byte-identical to what it
|
|
229
|
+
* was before this code existed. It also cannot see the condition through a re-emit: emit rewrites
|
|
230
|
+
* the preserved sequence into recognized mnemonics, and the resulting stream says that value
|
|
231
|
+
* unambiguously, so a second-generation read is silent and correct about its own bytes. The place
|
|
232
|
+
* to catch this is the first read of the wire bytes, which is where it now refuses a strict parse.
|
|
233
|
+
*/
|
|
234
|
+
ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE: "ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE",
|
|
235
|
+
/**
|
|
236
|
+
* Escape sequences are matched greedily and leftmost, so the escape character that **closed** an
|
|
237
|
+
* unrecognized sequence could not also **open** the next one. Where it could have, and where the
|
|
238
|
+
* body it would have held is the delimiter that was split on, the same bytes carry two alignments
|
|
239
|
+
* that disagree by one boundary: under the reading taken that delimiter ends a field, repeat or
|
|
240
|
+
* component, and under the other it sits inside an opaque atom and ends nothing. The leftmost
|
|
241
|
+
* reading is kept, every byte is preserved, and nothing is re-split.
|
|
242
|
+
*
|
|
243
|
+
* **This is the mirror of {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE}, and
|
|
244
|
+
* the direction is the reason it needs its own code.** That one reports a boundary the reading
|
|
245
|
+
* **lost**; this one reports a boundary the reading may have **gained**, which is the more
|
|
246
|
+
* dangerous direction, because a gained boundary hands back a value the sender's bytes do not
|
|
247
|
+
* unambiguously carry. Measured on the canonical set: `R|1|^^^687|28.6&Z&|&U/L||||F` reads
|
|
248
|
+
* `value` = `28.6&Z&` and `units` = `&U/L` under the leftmost alignment, and reads as a single
|
|
249
|
+
* unsplit field carrying both under the other.
|
|
250
|
+
*
|
|
251
|
+
* **Two exclusions, both deliberate, and the first is wider than its own argument.** Where the
|
|
252
|
+
* earlier sequence's body is a **recognized** mnemonic nothing is reported, because the reading
|
|
253
|
+
* taken interprets a construct (`&F&` is the sender escaping a field separator, which is what the
|
|
254
|
+
* mechanism is for) while the competing alignment's body is a delimiter character it usually
|
|
255
|
+
* cannot interpret at all, so this codec's own vocabulary prefers the reading taken. That is not
|
|
256
|
+
* the same as the reading taken being conformant, and where the declared set names a mnemonic
|
|
257
|
+
* letter as a splitting delimiter both alignments interpret one construct and neither is
|
|
258
|
+
* preferred, yet this stays silent. Both residues are measured and recorded rather than closed by
|
|
259
|
+
* widening the test. And where the escape
|
|
260
|
+
* character after the delimiter does not itself close a sequence there is no competing alignment
|
|
261
|
+
* at all, so an ordinary escaped value followed by an ordinary boundary is silent.
|
|
262
|
+
*
|
|
263
|
+
* **It is a report, not a repair, and not a round-trip guard.** The reading is unchanged: picking
|
|
264
|
+
* the other alignment would be a different guess with no more evidence behind it. Emit then
|
|
265
|
+
* rewrites the preserved sequences into recognized mnemonics, and those bytes carry the reading
|
|
266
|
+
* that was taken unambiguously, so a second-generation read is silent and is correct about its own
|
|
267
|
+
* bytes. The first read of the wire bytes is the only place the ambiguity exists to be caught.
|
|
268
|
+
*/
|
|
269
|
+
ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT: "ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT",
|
|
270
|
+
/**
|
|
271
|
+
* Two escape alignments of the same bytes disagreed about a **field** boundary, the reading taken
|
|
272
|
+
* kept it, and the escape character that reading resumes on **heads no sequence at all**, so the
|
|
273
|
+
* boundary was bought with a byte this reading cannot read while the competing alignment is
|
|
274
|
+
* exactly the reading that can. Every field after that point sits one place further right than the
|
|
275
|
+
* competing alignment puts it.
|
|
276
|
+
*
|
|
277
|
+
* **The shift is the harm, and on a result record it reaches the status slot.** Measured on the
|
|
278
|
+
* canonical set, `R|1|^^^687|28.6&F&|&U/L||||F` reads **9** fields under the reading taken and
|
|
279
|
+
* **8** under the competing one, so the sender's trailing `F` lands in field 9 (the result status)
|
|
280
|
+
* under the first and in no field at all under the second. The parse hands back units `&U/L` and a
|
|
281
|
+
* status of **`final`**, and both are consequences of the alignment rather than values the sender
|
|
282
|
+
* placed in those slots. A downstream system reading `final` would act on a result the bytes do
|
|
283
|
+
* not say was finalised. Before this code the only warning on that stream was the **tolerable**
|
|
284
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}, so the widest gate-legal profile plus
|
|
285
|
+
* `{ strict: true }` accepted it.
|
|
286
|
+
*
|
|
287
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical, and
|
|
288
|
+
* the units and status read are the ones that were always read. Picking the other alignment would
|
|
289
|
+
* be a different guess with no more evidence behind it, and it would change values on a published
|
|
290
|
+
* package. What is new is that the shift is reported by a code no profile may tolerate.
|
|
291
|
+
*
|
|
292
|
+
* **It fires alongside {@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT}, not instead
|
|
293
|
+
* of it, and neither test is a widening of the other.** That code asks whether this codec's
|
|
294
|
+
* vocabulary prefers the reading taken *at* the contested position, and is silent where the
|
|
295
|
+
* earlier body is a recognized mnemonic. This one asks what the reading taken makes of the bytes
|
|
296
|
+
* *after* the boundary, and does not consult the earlier body at all.
|
|
297
|
+
*
|
|
298
|
+
* **Two deliberate bounds, both stated rather than left to be found.**
|
|
299
|
+
* - **Only the field role, and that bound is a CHOICE rather than a consequence.** A gained repeat
|
|
300
|
+
* or component boundary divides one field and reaches nothing outside it, so it moves no
|
|
301
|
+
* **field-indexed** slot: the units and the status stay where they were. **It does not follow
|
|
302
|
+
* that it moves no modeled slot at all, and writing that down would be false.** Components are
|
|
303
|
+
* modeled *inside* a field: a Universal Test ID's four components are the LOINC-candidate slot,
|
|
304
|
+
* the test name, the **coding scheme** and the **local code**, and a patient name's three are
|
|
305
|
+
* last, first and middle. A gained component boundary shifts those, so a local code can be read
|
|
306
|
+
* as a coding scheme and a given name as a middle name. Measured, and **where the earlier body is
|
|
307
|
+
* a recognized mnemonic, reported by nothing** (only the tolerable
|
|
308
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER} fires, so a gate-legal profile accepts it).
|
|
309
|
+
* An **unrecognized** earlier body raises
|
|
310
|
+
* {@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT} there and is refused, which is the
|
|
311
|
+
* same split this code's own exclusion takes. It is a separate, open condition rather than something this
|
|
312
|
+
* code covers, because wiring this sink to another split is a different criterion needing its own
|
|
313
|
+
* population measurement. The repeat role costs the **value**, which this code does not report
|
|
314
|
+
* either.
|
|
315
|
+
* - **The tail is weighed one construct deep.** Where the escape character the reading taken
|
|
316
|
+
* resumes on heads a sequence this codec *recognizes*, the reading taken interprets it and the
|
|
317
|
+
* competing alignment would leave it bare, so the bytes prefer the reading taken: under a set
|
|
318
|
+
* naming the field separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and
|
|
319
|
+
* escaped again, and refusing it would be an over-refusal of a well-formed stream. Where it
|
|
320
|
+
* heads a sequence whose body is *unrecognized*, the reading taken still consumes it while the
|
|
321
|
+
* competing alignment would leave **two** escape characters bare, so the preference is stronger
|
|
322
|
+
* again, and this stays silent there even though the field shift is real. That last case is the
|
|
323
|
+
* named residue: measured, not overlooked.
|
|
324
|
+
*
|
|
325
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized mnemonics,
|
|
326
|
+
* and those bytes carry the reading that was taken unambiguously, so a second-generation read is
|
|
327
|
+
* silent and is correct about its own bytes. A clean re-read is not evidence.
|
|
328
|
+
*/
|
|
329
|
+
ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS: "ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS",
|
|
330
|
+
/**
|
|
331
|
+
* Two escape alignments of the same bytes disagreed about a **repeat** boundary, the reading
|
|
332
|
+
* taken kept it, and the escape character that reading resumes on **heads no sequence at all**.
|
|
333
|
+
* Same contested position and same tail test as
|
|
334
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS}, on a different delimiter role, and
|
|
335
|
+
* what it costs is a different thing, which is why it is a different code.
|
|
336
|
+
*
|
|
337
|
+
* **Nothing shifts, and that is not the same as nothing being lost.** No field-indexed slot
|
|
338
|
+
* moves: the units and the result status are read out of the same field numbers under either
|
|
339
|
+
* alignment. What this reports is that **the field is read as more repeats than the competing
|
|
340
|
+
* alignment gives it**, which is true wherever it fires. What that costs depends on **which**
|
|
341
|
+
* boundary was gained, and the two cases are stated separately rather than folded into one claim.
|
|
342
|
+
*
|
|
343
|
+
* **Where it is the FIRST boundary in the field, a modeled slot is lost**, because a field's
|
|
344
|
+
* modeled value and components are taken from its **first repeat alone**. Everything past the
|
|
345
|
+
* boundary stays on the wire and stays in `repeats`, and leaves every modeled slot:
|
|
346
|
+
*
|
|
347
|
+
* - **A value truncates.** Under `H|F^&`, `R|1|^^^687|28.6&S&F&U/L||||F` reads the value field as
|
|
348
|
+
* the two repeats `28.6^` and `&U/L`, so a consumer reads `28.6^` and the rest of the value is
|
|
349
|
+
* gone. The competing alignment reads one repeat carrying all of it.
|
|
350
|
+
* - **A modeled component list is deleted rather than shifted.** The same boundary inside a
|
|
351
|
+
* Universal Test ID leaves the components of the first repeat only: under `H|F^&`,
|
|
352
|
+
* `R|1|&F&F&687|28.6|U/L||||F` reads a UTID of one component holding a decoded field separator,
|
|
353
|
+
* with no coding scheme and no local code, where the bytes carry `687`. A patient name loses
|
|
354
|
+
* its given and middle names the same way. That is the half of this condition the shift report
|
|
355
|
+
* could not cover, because components are modeled **inside** a field.
|
|
356
|
+
*
|
|
357
|
+
* Before this code the only warnings on those streams were **tolerable** ones
|
|
358
|
+
* ({@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS} and, where the tail applies,
|
|
359
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}), so the widest gate-legal profile plus
|
|
360
|
+
* `{ strict: true }` accepted a truncated value and an emptied Universal Test ID.
|
|
361
|
+
*
|
|
362
|
+
* **Where it is a LATER boundary, no modeled slot moves and this still fires.** The first repeat
|
|
363
|
+
* is identical under both alignments, so the value, the components and every slot taken from them
|
|
364
|
+
* read the same either way (`R|1|^^^687|5.0F28.6&S&F&U/L||||F` under `H|F^&` reads a value of
|
|
365
|
+
* `5.0` under both), and what differs is the repeat structure after the first. It fires there
|
|
366
|
+
* deliberately: the boundary is still one the bytes do not force and a consumer reading `repeats`
|
|
367
|
+
* is still reading an alignment guess. Relative to the modeled slots that is **over-reporting,
|
|
368
|
+
* never under-reporting**. Narrowing it to the first boundary would change which streams a
|
|
369
|
+
* published package refuses and wants its own measurement, so the bound is written down instead.
|
|
370
|
+
*
|
|
371
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical,
|
|
372
|
+
* `repeats` still carries every one of them, and the value read is the value that was always
|
|
373
|
+
* read. Picking the other alignment would be a different guess with no more evidence behind it,
|
|
374
|
+
* on a published package.
|
|
375
|
+
*
|
|
376
|
+
* **Two further deliberate bounds, both stated rather than left to be found.**
|
|
377
|
+
* - **Only the repeat role.** The **component** role reaches a modeled slot too, and differently:
|
|
378
|
+
* there the components stay in the record and move one slot along, so a local code reads as a
|
|
379
|
+
* coding scheme and a given name as a middle name. That is measured, reported by nothing where
|
|
380
|
+
* the earlier body is a recognized mnemonic, and open. It is not covered here, because wiring a
|
|
381
|
+
* sink to another split is another criterion wanting its own population measurement.
|
|
382
|
+
* - **The tail is weighed one construct deep**, exactly as for the shift report. Where the escape
|
|
383
|
+
* character the reading resumes on heads a sequence this codec *recognizes*, the bytes prefer
|
|
384
|
+
* the reading taken and the repeats are the ones the sender wrote: under a set naming the
|
|
385
|
+
* repeat separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again,
|
|
386
|
+
* and refusing it would be an over-refusal of a well-formed stream. Where it heads a sequence
|
|
387
|
+
* whose body is *unrecognized*, the competing alignment would leave **two** escape characters
|
|
388
|
+
* bare, so the preference is stronger again, and this stays silent there even though the gained
|
|
389
|
+
* boundary is real. That last case is the named residue: measured, not overlooked.
|
|
390
|
+
*
|
|
391
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized
|
|
392
|
+
* mnemonics, and those bytes carry the reading that was taken unambiguously, so a
|
|
393
|
+
* second-generation read is silent and is correct about its own bytes. A clean re-read is not
|
|
394
|
+
* evidence.
|
|
395
|
+
*/
|
|
396
|
+
ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD: "ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD",
|
|
157
397
|
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
158
398
|
ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
159
399
|
/**
|
|
@@ -175,7 +415,8 @@ var WARNING_CODES = {
|
|
|
175
415
|
* *different* escape character in the same record may still head a real three-character sequence,
|
|
176
416
|
* and if that sequence's body happens to be a delimiter (`&|&` under the canonical set) the atom
|
|
177
417
|
* rule means that delimiter does not split. That case is reported separately, under
|
|
178
|
-
* {@link WARNING_CODES.
|
|
418
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE}, and it still costs a field
|
|
419
|
+
* boundary.
|
|
179
420
|
*/
|
|
180
421
|
ASTM_UNPAIRED_ESCAPE_CHARACTER: "ASTM_UNPAIRED_ESCAPE_CHARACTER",
|
|
181
422
|
/**
|
|
@@ -241,7 +482,8 @@ var WARNING_CODES = {
|
|
|
241
482
|
*/
|
|
242
483
|
ASTM_RECORD_DELIMITERS_REDECLARED: "ASTM_RECORD_DELIMITERS_REDECLARED",
|
|
243
484
|
/**
|
|
244
|
-
* A later `H` (header) record could not declare a usable delimiter set: it was
|
|
485
|
+
* A later `H` (header) record could not declare a usable delimiter set: it was not a header, it
|
|
486
|
+
* was too short, its delimiter definition held fewer than three characters, or the
|
|
245
487
|
* field separator it named also appeared among the other three, leaving the four roles
|
|
246
488
|
* indistinguishable. The delimiters **already in force are kept** and every record is still surfaced;
|
|
247
489
|
* a set is never guessed and no record is dropped.
|
|
@@ -283,6 +525,41 @@ function nonStandardDelimiters(position) {
|
|
|
283
525
|
position
|
|
284
526
|
};
|
|
285
527
|
}
|
|
528
|
+
function delimiterRoleCollision(position) {
|
|
529
|
+
return {
|
|
530
|
+
code: WARNING_CODES.ASTM_RECORD_DELIMITER_ROLE_COLLISION,
|
|
531
|
+
message: "Header declared one character in two delimiter roles, honored as declared; the boundary between those two roles cannot be recovered from the bytes, and emit refuses such a set.",
|
|
532
|
+
position
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function delimiterSwallowedByEscape(position) {
|
|
536
|
+
return {
|
|
537
|
+
code: WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE,
|
|
538
|
+
message: "An unrecognized escape sequence held a delimiter in force, so that delimiter did not end a field, repeat or component. The sequence is preserved verbatim and nothing is re-split.",
|
|
539
|
+
position
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function ambiguousEscapeAlignment(position) {
|
|
543
|
+
return {
|
|
544
|
+
code: WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT,
|
|
545
|
+
message: "An unrecognized escape sequence ended where another one could have begun, so a delimiter that ended a field, repeat or component here sits inside a sequence under the other alignment. The leftmost alignment was kept, every byte is preserved, and nothing is re-split.",
|
|
546
|
+
position
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function alignmentShiftedFields(position) {
|
|
550
|
+
return {
|
|
551
|
+
code: WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS,
|
|
552
|
+
message: "A field boundary here is one of two escape alignments the bytes carry, and the reading that took it resumes on an escape character heading no sequence, which the other alignment uses to close one. Every later field is one place further right than the other reading puts it, so a result's units and status may be read out of slots the sender did not put them in. The reading was kept and every byte is preserved.",
|
|
553
|
+
position
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function alignmentTruncatedField(position) {
|
|
557
|
+
return {
|
|
558
|
+
code: WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD,
|
|
559
|
+
message: "A repeat boundary here is one of two escape alignments the bytes carry, and the reading that took it resumes on an escape character heading no sequence, which the other alignment uses to close one. This field is read as more repeats than the other reading gives it, and its modeled value and components are taken from the first repeat alone, so where that boundary is the first one the rest of the field leaves every modeled slot. The reading was kept and every byte is preserved.",
|
|
560
|
+
position
|
|
561
|
+
};
|
|
562
|
+
}
|
|
286
563
|
function delimitersRedeclared(position) {
|
|
287
564
|
return {
|
|
288
565
|
code: WARNING_CODES.ASTM_RECORD_DELIMITERS_REDECLARED,
|
|
@@ -898,7 +1175,7 @@ var defaultProfile = defineAstmProfile({
|
|
|
898
1175
|
// src/profiles/reference-corpus.ts
|
|
899
1176
|
var referenceCorpus = defineAstmProfile({
|
|
900
1177
|
name: "referenceCorpus",
|
|
901
|
-
description: "Non-standard-escape tolerance grounded firsthand in the escape-agnostic OSS reference corpus (python-astm / senaite). Mostly syntactic encoding noise, with one measured exception: an escape sequence whose body is itself a delimiter is an opaque atom, so that delimiter does not split and a result can lose its units and its status. Read the values; do not rely on this profile leaving only cosmetic deviations behind.",
|
|
1178
|
+
description: "Non-standard-escape tolerance grounded firsthand in the escape-agnostic OSS reference corpus (python-astm / senaite). Mostly syntactic encoding noise, with one measured exception: an escape sequence whose body is an unrecognized character that is itself a delimiter in force is an opaque atom, so that delimiter does not split and a result can lose its units and its status. The mirror case is measured too: sequences are matched leftmost, so one can end where another could have begun and a delimiter splits that the other alignment would have held. Read the values; do not rely on this profile leaving only cosmetic deviations behind.",
|
|
902
1179
|
provenance: {
|
|
903
1180
|
source: "kxepal/python-astm codec.py (BSD) + senaite.astm / senaite.lis2a",
|
|
904
1181
|
reference: "https://github.com/kxepal/python-astm/blob/master/astm/codec.py ; https://github.com/senaite/senaite.astm",
|
|
@@ -908,7 +1185,7 @@ var referenceCorpus = defineAstmProfile({
|
|
|
908
1185
|
tolerate: [
|
|
909
1186
|
{
|
|
910
1187
|
code: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
911
|
-
rationale: "The OSS reference stack (python-astm codec.py, firsthand-verified) treats '&' as literal data and never decodes escape sequences, so streams from that ecosystem carry non-standard '&\u2026&' bodies our escape-aware tokenizer flags. The body is preserved byte-for-byte in the value. NOT always non-clinical, and this is measured rather than assumed: where the body is a delimiter, that delimiter never became a field boundary, so `28.6&|&U/L` reads as one value with no units and no final status.
|
|
1188
|
+
rationale: "The OSS reference stack (python-astm codec.py, firsthand-verified) treats '&' as literal data and never decodes escape sequences, so streams from that ecosystem carry non-standard '&\u2026&' bodies our escape-aware tokenizer flags. The body is preserved byte-for-byte in the value. NOT always non-clinical, and this is measured rather than assumed: where the body is a delimiter, that delimiter never became a field boundary, so `28.6&|&U/L` reads as one value with no units and no final status. That subset now also raises ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE, which this profile cannot tolerate, so tolerating this code no longer hides it. The same holds for the mirror case, where a sequence ends where another could have begun and a delimiter splits that the other alignment would have held: that raises ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT, which this profile cannot tolerate either. Where that gained boundary is a field boundary and the reading taken resumes on an escape character heading no sequence, every later field shifts and a result's units and status move with it, which raises ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS, untolerable here as well. Where that same gained boundary is a repeat boundary, nothing shifts but the field is read out of its first repeat alone, so a value truncates and a test identity empties, which raises ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD, untolerable here too. Both of those carry the same tail bound and stay silent where the escape character past the boundary heads a sequence of its own, recognized or not, which is much of this corpus: tolerating this code does not quiet them, but their absence is not a statement that nothing was lost."
|
|
912
1189
|
}
|
|
913
1190
|
]
|
|
914
1191
|
});
|
|
@@ -936,7 +1213,7 @@ function getDefaultAstmProfile() {
|
|
|
936
1213
|
function isEscapeSequenceAt(text, i, escape) {
|
|
937
1214
|
return text.charAt(i) === escape && text.charAt(i + 2) === escape;
|
|
938
1215
|
}
|
|
939
|
-
function decodeEscapes(leaf, d, onUnknown, onUnpaired) {
|
|
1216
|
+
function decodeEscapes(leaf, d, onUnknown, onUnpaired, onSwallowedDelimiter) {
|
|
940
1217
|
const esc = d.escape;
|
|
941
1218
|
if (!leaf.includes(esc)) return leaf;
|
|
942
1219
|
let out = "";
|
|
@@ -959,6 +1236,7 @@ function decodeEscapes(leaf, d, onUnknown, onUnpaired) {
|
|
|
959
1236
|
if (replacement === void 0) {
|
|
960
1237
|
out += esc + body + esc;
|
|
961
1238
|
onUnknown?.();
|
|
1239
|
+
if (isSplittingDelimiter(body, d)) onSwallowedDelimiter?.();
|
|
962
1240
|
} else {
|
|
963
1241
|
out += replacement;
|
|
964
1242
|
}
|
|
@@ -966,21 +1244,22 @@ function decodeEscapes(leaf, d, onUnknown, onUnpaired) {
|
|
|
966
1244
|
}
|
|
967
1245
|
return out;
|
|
968
1246
|
}
|
|
1247
|
+
function isSplittingDelimiter(body, d) {
|
|
1248
|
+
return body === d.field || body === d.repeat || body === d.component;
|
|
1249
|
+
}
|
|
1250
|
+
var MNEMONIC_ROLES = {
|
|
1251
|
+
F: "field",
|
|
1252
|
+
S: "component",
|
|
1253
|
+
R: "repeat",
|
|
1254
|
+
E: "escape"
|
|
1255
|
+
};
|
|
1256
|
+
function isMnemonicBody(body) {
|
|
1257
|
+
return Object.hasOwn(MNEMONIC_ROLES, body);
|
|
1258
|
+
}
|
|
969
1259
|
function escapeBody(body, d) {
|
|
970
|
-
|
|
971
|
-
case "F":
|
|
972
|
-
return d.field;
|
|
973
|
-
case "S":
|
|
974
|
-
return d.component;
|
|
975
|
-
case "R":
|
|
976
|
-
return d.repeat;
|
|
977
|
-
case "E":
|
|
978
|
-
return d.escape;
|
|
979
|
-
default:
|
|
980
|
-
return void 0;
|
|
981
|
-
}
|
|
1260
|
+
return isMnemonicBody(body) ? d[MNEMONIC_ROLES[body]] : void 0;
|
|
982
1261
|
}
|
|
983
|
-
function splitEscapeAware(text, delimiter, escape) {
|
|
1262
|
+
function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShiftedFields, onTruncatedField) {
|
|
984
1263
|
if (text.length === 0) return [""];
|
|
985
1264
|
const out = [];
|
|
986
1265
|
let current = "";
|
|
@@ -988,6 +1267,14 @@ function splitEscapeAware(text, delimiter, escape) {
|
|
|
988
1267
|
while (i < text.length) {
|
|
989
1268
|
const ch = text.charAt(i);
|
|
990
1269
|
if (ch === escape && isEscapeSequenceAt(text, i, escape)) {
|
|
1270
|
+
const contested = text.charAt(i + 3) === delimiter && text.charAt(i + 4) === escape;
|
|
1271
|
+
if (contested && !isMnemonicBody(text.charAt(i + 1))) {
|
|
1272
|
+
onAmbiguousAlignment?.(out.length);
|
|
1273
|
+
}
|
|
1274
|
+
if (contested && !isEscapeSequenceAt(text, i + 4, escape)) {
|
|
1275
|
+
onShiftedFields?.(out.length);
|
|
1276
|
+
onTruncatedField?.(out.length);
|
|
1277
|
+
}
|
|
991
1278
|
current += text.slice(i, i + 3);
|
|
992
1279
|
i += 3;
|
|
993
1280
|
continue;
|
|
@@ -1006,18 +1293,30 @@ function splitEscapeAware(text, delimiter, escape) {
|
|
|
1006
1293
|
}
|
|
1007
1294
|
|
|
1008
1295
|
// src/records/tokenize.ts
|
|
1009
|
-
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape) {
|
|
1010
|
-
const rawFields = splitEscapeAware(
|
|
1296
|
+
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1297
|
+
const rawFields = splitEscapeAware(
|
|
1298
|
+
record,
|
|
1299
|
+
d.field,
|
|
1300
|
+
d.escape,
|
|
1301
|
+
(fieldIndex) => onAmbiguousAlignment?.(fieldIndex),
|
|
1302
|
+
(fieldIndex) => onAlignmentShiftedFields?.(fieldIndex)
|
|
1303
|
+
);
|
|
1011
1304
|
return rawFields.map(
|
|
1012
1305
|
(raw, fieldIndex) => toField(
|
|
1013
1306
|
raw,
|
|
1014
1307
|
d,
|
|
1015
1308
|
() => onUnknownEscape?.(fieldIndex),
|
|
1016
|
-
() => onUnpairedEscape?.(fieldIndex)
|
|
1309
|
+
() => onUnpairedEscape?.(fieldIndex),
|
|
1310
|
+
() => onSwallowedDelimiter?.(fieldIndex),
|
|
1311
|
+
() => onAmbiguousAlignment?.(fieldIndex),
|
|
1312
|
+
// The repeat split runs INSIDE one field, so the index it hands back is a repeat index and is
|
|
1313
|
+
// discarded: what a caller needs is which field carries the contested boundary, and that is
|
|
1314
|
+
// the index this map is already iterating.
|
|
1315
|
+
() => onAlignmentTruncatedField?.(fieldIndex)
|
|
1017
1316
|
)
|
|
1018
1317
|
);
|
|
1019
1318
|
}
|
|
1020
|
-
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape) {
|
|
1319
|
+
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1021
1320
|
const defEnd = record.indexOf(d.field, 2);
|
|
1022
1321
|
const definition = defEnd === -1 ? record.slice(2) : record.slice(2, defEnd);
|
|
1023
1322
|
const head = [opaqueField(record.slice(0, Math.min(1, record.length))), opaqueField(definition)];
|
|
@@ -1026,18 +1325,35 @@ function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape) {
|
|
|
1026
1325
|
record.slice(defEnd + 1),
|
|
1027
1326
|
d,
|
|
1028
1327
|
(i) => onUnknownEscape?.(i + 2),
|
|
1029
|
-
(i) => onUnpairedEscape?.(i + 2)
|
|
1328
|
+
(i) => onUnpairedEscape?.(i + 2),
|
|
1329
|
+
(i) => onSwallowedDelimiter?.(i + 2),
|
|
1330
|
+
(i) => onAmbiguousAlignment?.(i + 2),
|
|
1331
|
+
(i) => onAlignmentShiftedFields?.(i + 2),
|
|
1332
|
+
(i) => onAlignmentTruncatedField?.(i + 2)
|
|
1030
1333
|
);
|
|
1031
1334
|
return [...head, ...data];
|
|
1032
1335
|
}
|
|
1033
1336
|
function opaqueField(raw) {
|
|
1034
1337
|
return { raw, components: [raw], repeats: [[raw]] };
|
|
1035
1338
|
}
|
|
1036
|
-
function toField(raw, d, onUnknownEscape, onUnpairedEscape) {
|
|
1037
|
-
const rawRepeats = splitEscapeAware(
|
|
1339
|
+
function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentTruncatedField) {
|
|
1340
|
+
const rawRepeats = splitEscapeAware(
|
|
1341
|
+
raw,
|
|
1342
|
+
d.repeat,
|
|
1343
|
+
d.escape,
|
|
1344
|
+
() => {
|
|
1345
|
+
onAmbiguousAlignment();
|
|
1346
|
+
},
|
|
1347
|
+
void 0,
|
|
1348
|
+
() => {
|
|
1349
|
+
onAlignmentTruncatedField();
|
|
1350
|
+
}
|
|
1351
|
+
);
|
|
1038
1352
|
const repeats = rawRepeats.map(
|
|
1039
|
-
(rep) => splitEscapeAware(rep, d.component, d.escape)
|
|
1040
|
-
(
|
|
1353
|
+
(rep) => splitEscapeAware(rep, d.component, d.escape, () => {
|
|
1354
|
+
onAmbiguousAlignment();
|
|
1355
|
+
}).map(
|
|
1356
|
+
(comp) => decodeEscapes(comp, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter)
|
|
1041
1357
|
)
|
|
1042
1358
|
);
|
|
1043
1359
|
const components = repeats[0] ?? [""];
|
|
@@ -1074,18 +1390,22 @@ function parseAstmRecords(raw, options = {}) {
|
|
|
1074
1390
|
leadType !== void 0 && leadType.length > 0 ? { recordIndex: 0, recordType: leadType } : { recordIndex: 0 }
|
|
1075
1391
|
);
|
|
1076
1392
|
}
|
|
1077
|
-
const
|
|
1078
|
-
if (
|
|
1393
|
+
const declaration = readDelimiterDeclaration(first);
|
|
1394
|
+
if (!declaration.ok) {
|
|
1079
1395
|
throw new AstmParseError(
|
|
1080
1396
|
FATAL_CODES.ASTM_RECORD_UNDECLARED_DELIMITERS,
|
|
1081
|
-
|
|
1397
|
+
UNDECLARED_DELIMITER_MESSAGES[declaration.fault],
|
|
1082
1398
|
{ recordIndex: 0, recordType: "H" }
|
|
1083
1399
|
);
|
|
1084
1400
|
}
|
|
1401
|
+
const delimiters = declaration.delimiters;
|
|
1085
1402
|
const warnings = [];
|
|
1086
1403
|
if (isNonStandard(delimiters)) {
|
|
1087
1404
|
warnings.push(nonStandardDelimiters({ recordIndex: 0, recordType: "H" }));
|
|
1088
1405
|
}
|
|
1406
|
+
if (hasCollidingRoles(delimiters)) {
|
|
1407
|
+
warnings.push(delimiterRoleCollision({ recordIndex: 0, recordType: "H" }));
|
|
1408
|
+
}
|
|
1089
1409
|
let active = delimiters;
|
|
1090
1410
|
const built = lines.map((line2, recordIndex) => {
|
|
1091
1411
|
if (recordIndex > 0 && line2.charAt(0) === "H") {
|
|
@@ -1117,6 +1437,12 @@ function parseAstmRecords(raw, options = {}) {
|
|
|
1117
1437
|
} : { header, records, delimiters, classification, warnings: finalWarnings };
|
|
1118
1438
|
return deepFreeze(message);
|
|
1119
1439
|
}
|
|
1440
|
+
var UNDECLARED_DELIMITER_MESSAGES = {
|
|
1441
|
+
"not-a-header": "Header record does not begin with an H type letter.",
|
|
1442
|
+
"record-too-short": "Header record is too short to declare the four delimiters.",
|
|
1443
|
+
"definition-truncated": "Header record declares fewer than three delimiter-definition characters before its next field separator, so the repeat, component and escape roles are not all named.",
|
|
1444
|
+
"field-separator-reused": "Header record names its field separator in one of the other three delimiter roles, so the four roles are not distinguishable."
|
|
1445
|
+
};
|
|
1120
1446
|
function resolveProfile(options) {
|
|
1121
1447
|
if (options.profile === null) return void 0;
|
|
1122
1448
|
if (options.profile !== void 0) return options.profile;
|
|
@@ -1132,6 +1458,7 @@ function adoptRedeclaredDelimiters(line2, active, recordIndex, warnings) {
|
|
|
1132
1458
|
if (sameDelimiters(declared, active)) return active;
|
|
1133
1459
|
warnings.push(delimitersRedeclared(position));
|
|
1134
1460
|
if (isNonStandard(declared)) warnings.push(nonStandardDelimiters(position));
|
|
1461
|
+
if (hasCollidingRoles(declared)) warnings.push(delimiterRoleCollision(position));
|
|
1135
1462
|
return declared;
|
|
1136
1463
|
}
|
|
1137
1464
|
function sameDelimiters(a, b) {
|
|
@@ -1157,7 +1484,45 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1157
1484
|
unpairedEscapeCharacter({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1158
1485
|
);
|
|
1159
1486
|
};
|
|
1160
|
-
const
|
|
1487
|
+
const onSwallowedDelimiter = (fieldIndex) => {
|
|
1488
|
+
warnings.push(
|
|
1489
|
+
delimiterSwallowedByEscape({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1490
|
+
);
|
|
1491
|
+
};
|
|
1492
|
+
const onAmbiguousAlignment = (fieldIndex) => {
|
|
1493
|
+
warnings.push(
|
|
1494
|
+
ambiguousEscapeAlignment({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1495
|
+
);
|
|
1496
|
+
};
|
|
1497
|
+
const onAlignmentShiftedFields = (fieldIndex) => {
|
|
1498
|
+
warnings.push(
|
|
1499
|
+
alignmentShiftedFields({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1500
|
+
);
|
|
1501
|
+
};
|
|
1502
|
+
const onAlignmentTruncatedField = (fieldIndex) => {
|
|
1503
|
+
warnings.push(
|
|
1504
|
+
alignmentTruncatedField({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1505
|
+
);
|
|
1506
|
+
};
|
|
1507
|
+
const fields = rawType === "H" ? tokenizeHeader(
|
|
1508
|
+
line2,
|
|
1509
|
+
d,
|
|
1510
|
+
onUnknownEscape,
|
|
1511
|
+
onUnpairedEscape,
|
|
1512
|
+
onSwallowedDelimiter,
|
|
1513
|
+
onAmbiguousAlignment,
|
|
1514
|
+
onAlignmentShiftedFields,
|
|
1515
|
+
onAlignmentTruncatedField
|
|
1516
|
+
) : tokenizeRecord(
|
|
1517
|
+
line2,
|
|
1518
|
+
d,
|
|
1519
|
+
onUnknownEscape,
|
|
1520
|
+
onUnpairedEscape,
|
|
1521
|
+
onSwallowedDelimiter,
|
|
1522
|
+
onAmbiguousAlignment,
|
|
1523
|
+
onAlignmentShiftedFields,
|
|
1524
|
+
onAlignmentTruncatedField
|
|
1525
|
+
);
|
|
1161
1526
|
if (fields.length === 1 && line2.slice(1).trim().length > 0) {
|
|
1162
1527
|
warnings.push(fieldsUnseparated({ recordIndex, recordType: rawType }));
|
|
1163
1528
|
}
|
|
@@ -2369,8 +2734,8 @@ function concatBytes3(chunks) {
|
|
|
2369
2734
|
}
|
|
2370
2735
|
|
|
2371
2736
|
// src/index.ts
|
|
2372
|
-
var VERSION = "0.0.
|
|
2737
|
+
var VERSION = "0.0.18";
|
|
2373
2738
|
|
|
2374
|
-
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, AstmAmbiguousStreamError, AstmFrameEncodeError, AstmFrameStrictError, AstmParseError, AstmProfileDefinitionError, AstmSerializeError, AstmStrictError, CANONICAL_DELIMITERS, FATAL_CODES, FRAME_WARNING_CODES, LIVD_WARNING_CODES, LTP_WARNING_CODES, SAFETY_CRITICAL_CODES, TOLERABLE_CODES, VERSION, WARNING_CODES, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
|
2739
|
+
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, AstmAmbiguousStreamError, AstmFrameEncodeError, AstmFrameStrictError, AstmParseError, AstmProfileDefinitionError, AstmSerializeError, AstmStrictError, CANONICAL_DELIMITERS, FATAL_CODES, FRAME_WARNING_CODES, LIVD_WARNING_CODES, LTP_WARNING_CODES, SAFETY_CRITICAL_CODES, TOLERABLE_CODES, VERSION, WARNING_CODES, alignmentShiftedFields, alignmentTruncatedField, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
|
2375
2740
|
//# sourceMappingURL=index.mjs.map
|
|
2376
2741
|
//# sourceMappingURL=index.mjs.map
|