@cosyte/astm 0.0.17 → 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 +73 -1
- package/README.md +28 -7
- package/dist/index.cjs +110 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +249 -23
- package/dist/index.d.ts +249 -23
- package/dist/index.mjs +110 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -173,7 +173,7 @@ this file is maintained by hand (Changesets handles the version bump and publish
|
|
|
173
173
|
profile, and both are **`PRE-EXISTING`** (byte-identical on the base of this slice), so they are
|
|
174
174
|
disclosed here as a separate open condition rather than covered: wiring this sink to another
|
|
175
175
|
delimiter role is a different criterion and wants its own population measurement. On the repeat role
|
|
176
|
-
what is lost is the value,
|
|
176
|
+
what is lost is the value, not reported by this code and now reported by its own, below. And the tail is weighed **one construct deep**: where
|
|
177
177
|
the escape character past the boundary heads a **recognized** sequence, the alignment taken
|
|
178
178
|
interprets it while the competing one would leave it bare, so the bytes prefer the alignment taken
|
|
179
179
|
(under a set naming the field separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written and
|
|
@@ -204,6 +204,78 @@ this file is maintained by hand (Changesets handles the version bump and publish
|
|
|
204
204
|
`ShiftedFieldsSink` type, and a trailing optional sink parameter on `splitEscapeAware`,
|
|
205
205
|
`tokenizeRecord` and `tokenizeHeader`.
|
|
206
206
|
|
|
207
|
+
- **A parse-path warning code for a field a competing escape alignment read short**
|
|
208
|
+
(`ASTM-FRAME-RESIDUALS`, defect 17b). `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` fires where two
|
|
209
|
+
escape alignments of the same bytes disagree about a **repeat** boundary, the reading taken keeps
|
|
210
|
+
it, and the escape character that reading resumes on heads **no sequence at all**. It lands on the
|
|
211
|
+
profile safety gate, so `{ strict: true }` refuses a stream carrying it whatever profile is in
|
|
212
|
+
force.
|
|
213
|
+
|
|
214
|
+
**Nothing shifts, and that is not the same as nothing being lost, which is the correction this
|
|
215
|
+
entry carries.** No field-indexed slot moves: the units slot and the result-status slot are read
|
|
216
|
+
out of the same field numbers under either alignment, which is exactly why the shift report above
|
|
217
|
+
cannot see this. What it reports is that **the field is read as more repeats than the competing
|
|
218
|
+
alignment gives it**, which is true wherever it fires. **Where that gained boundary is the FIRST
|
|
219
|
+
one in the field it reaches a modeled slot**, because a field's modeled value and its components
|
|
220
|
+
are taken from its first repeat alone: everything past the boundary stays on the wire, stays in
|
|
221
|
+
`repeats`, and leaves every modeled slot. Both costs are reachable on the **canonical** set, so no
|
|
222
|
+
unusual declaration is needed for either:
|
|
223
|
+
- **A value truncates.** `R|1|^^^687|28.6&S&\&U/L|U/L||||F` reads a value of `28.6^`, and `&U/L`
|
|
224
|
+
leaves the result entirely; the competing alignment reads one repeat carrying all of it.
|
|
225
|
+
- **A modeled identity empties, which is the half the shift report could not reach.**
|
|
226
|
+
`R|1|&F&\&687|28.6|U/L||||F` reads a Universal Test ID of one component holding a decoded
|
|
227
|
+
field separator, so the local code `687` is in no modeled slot at all and the identity comes back
|
|
228
|
+
as a LOINC candidate nobody wrote. `DOE&S&\&JANE^A` reads a last name and **no given or middle
|
|
229
|
+
name**. Before this code the only warnings on those streams were tolerable ones, so the widest
|
|
230
|
+
gate-legal profile plus `{ strict: true }` accepted a truncated value and an emptied test
|
|
231
|
+
identity.
|
|
232
|
+
|
|
233
|
+
**At a LATER boundary it fires and no modeled slot moves**, because the first repeat is then
|
|
234
|
+
identical under both readings, and what differs is the repeat structure after it. That is
|
|
235
|
+
**over-reporting relative to the modeled slots and never under-reporting**, it is measured on its
|
|
236
|
+
own sweep beside the main corpus rather than assumed, and it is stated on the code itself.
|
|
237
|
+
Narrowing the sink to the first boundary would change which streams a published package refuses
|
|
238
|
+
and wants its own measurement, so the bound is written down instead.
|
|
239
|
+
|
|
240
|
+
**Additive, and the split is unchanged.** Every decoded byte is identical, `repeats` still carries
|
|
241
|
+
every one of them, and the value read is the value that was always read: this reports the gained
|
|
242
|
+
boundary, it does not repair it. Picking the other alignment was rejected for the same reason as
|
|
243
|
+
before, on a published package.
|
|
244
|
+
|
|
245
|
+
**Two further bounds, both deliberate.** It is wired to the **repeat** separator only: a gained
|
|
246
|
+
**component** boundary reaches a modeled slot too, and differently, moving it one slot along rather
|
|
247
|
+
than dropping it, and that stays the separate open condition disclosed above. And the tail is
|
|
248
|
+
weighed **one construct deep**, on the same test the shift report uses: where the escape character
|
|
249
|
+
past the boundary heads a **recognized** sequence the bytes prefer the reading taken and the two
|
|
250
|
+
repeats are the ones the sender wrote (`28.6&R&\&R&U/L` is the canonical repeat separator
|
|
251
|
+
escaped, written and escaped again, and refusing it is the over-refusal that sank a criterion
|
|
252
|
+
measured for this same family), and where it heads an **unrecognized** one the competing alignment
|
|
253
|
+
would leave two escape characters bare, so the preference is stronger again. That second case still
|
|
254
|
+
truncates and is silent here: recorded as a residue, measured, not overlooked.
|
|
255
|
+
|
|
256
|
+
**Measured against the corpus constants committed with the test**, on the same
|
|
257
|
+
strict-accepted-under-a-gate-legal-profile tier and the same **864 tuples** as the two measurements
|
|
258
|
+
before it, in `test/records/alignment-truncated-field.test.ts`: the code fires on **96**, moves
|
|
259
|
+
**32** from accepted to refused under a profile built from the whole tolerable allow-list, moves
|
|
260
|
+
**0** back, and fires on **none** of the 96 escape-clean tuples. The pair-count criterion measured
|
|
261
|
+
and rejected on this same corpus would have refused 48 of them, which is why the tail is what gets
|
|
262
|
+
weighed. **Unlike that rejected criterion's population, this one is not confined to a set naming a
|
|
263
|
+
mnemonic letter as a delimiter**: the canonical repeat separator reaches it, on 12 of the 108
|
|
264
|
+
canonical tuples swept. A second sweep beside it moves the contested boundary off the front of the
|
|
265
|
+
field and measures the same column: **96 fire, 32 move, 0 back, 0 escape-clean**, and on every one
|
|
266
|
+
of the 96 the first repeat is identical under both alignments, which is the bound on the claim
|
|
267
|
+
stated as a measurement rather than as prose.
|
|
268
|
+
|
|
269
|
+
**It does not survive a re-emit**, the same residue its siblings disclosed: the serializer rewrites
|
|
270
|
+
the preserved characters into recognized mnemonics, so a second-generation read is silent and is
|
|
271
|
+
correct about its own bytes. Catch it on the first read of the wire bytes.
|
|
272
|
+
|
|
273
|
+
This is a **narrowing on a published package**, on the strict path only: a lenient parse of the
|
|
274
|
+
same stream returns the same records, with the same values, and one more warning on them. New
|
|
275
|
+
public surface: `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD`, the `alignmentTruncatedField` warning
|
|
276
|
+
factory, the `TruncatedFieldSink` type, and a further trailing optional sink parameter on
|
|
277
|
+
`splitEscapeAware`, `tokenizeRecord` and `tokenizeHeader`.
|
|
278
|
+
|
|
207
279
|
### Fixed
|
|
208
280
|
|
|
209
281
|
- **A header that is not short no longer reports that it is too short** (`ASTM-FRAME-RESIDUALS`,
|
package/README.md
CHANGED
|
@@ -235,12 +235,30 @@ tolerate**; before it existed the only warning on that stream was the tolerable
|
|
|
235
235
|
unchanged: it reports the shift rather than repairing it. It is wired to the **field** separator
|
|
236
236
|
only, because a gained repeat or component boundary divides one field and so moves no field-indexed
|
|
237
237
|
slot. **That bound is a choice, not a consequence**: components are modeled inside a field, so a
|
|
238
|
-
gained
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
238
|
+
gained repeat or component boundary does reach a modeled slot. It is also silent where the trailing
|
|
239
|
+
escape character heads a sequence of its own, recognized or not; that case still shifts the fields
|
|
240
|
+
and is the recorded residue. It does not survive a re-emit either: catch it on the first read.
|
|
241
|
+
|
|
242
|
+
**And where that gained boundary is a REPEAT boundary, nothing shifts and the field can still be
|
|
243
|
+
read short.** What the report says is that the field is read as more repeats than the competing
|
|
244
|
+
alignment gives it. A field's modeled value and components are taken from its **first repeat
|
|
245
|
+
alone**, so where the gained boundary is the **first** one everything past it stays on the wire,
|
|
246
|
+
stays in `repeats`, and leaves every modeled slot. On the canonical set, `R|1|^^^687|28.6&S&\&U/L|U/L||||F` reads a value of `28.6^`
|
|
247
|
+
and drops `&U/L`, and `R|1|&F&\&687|28.6|U/L||||F` reads a Universal Test ID of one component
|
|
248
|
+
holding a decoded field separator, so the local code `687` is in no modeled slot and the identity
|
|
249
|
+
comes back as a LOINC candidate nobody wrote. A patient name loses its given and middle names the
|
|
250
|
+
same way. That raises `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD`, which **no profile may tolerate**;
|
|
251
|
+
before it existed the only warnings on those streams were tolerable ones, so a strict parse under a
|
|
252
|
+
legal profile accepted a truncated value and an emptied test identity. The reading is unchanged: it
|
|
253
|
+
reports the gained boundary rather than repairing it. **At a LATER boundary it fires and no modeled
|
|
254
|
+
slot moves**, because the first repeat is then the same under both readings; that is over-reporting
|
|
255
|
+
relative to the modeled slots and never under-reporting, and it is measured rather than assumed. Its
|
|
256
|
+
tail bound is the shift report's, for the same reason: `28.6&R&\&R&U/L` is the repeat separator
|
|
257
|
+
escaped, written, and escaped again, and refusing it would refuse a well-formed stream. It is wired to the **repeat** separator only; a gained
|
|
258
|
+
**component** boundary reaches a modeled slot differently, moving it one slot along rather than
|
|
259
|
+
dropping it (`R|1|&F&^&GLU^L^687|28.6|U/L||||F` reads `687` as a local code under one alignment and
|
|
260
|
+
as the coding scheme under the other, with only a tolerable code raised), and that is a separate
|
|
261
|
+
open condition. It does not survive a re-emit either: catch it on the first read.
|
|
244
262
|
|
|
245
263
|
### A header that names one character in two delimiter roles
|
|
246
264
|
|
|
@@ -339,7 +357,10 @@ outside it:
|
|
|
339
357
|
not tolerable. Where that gained boundary is a **field** boundary and the reading taken resumes on
|
|
340
358
|
an escape character heading no sequence, every later field shifts one place and a result's units
|
|
341
359
|
and status are read out of slots the other alignment does not put them in: that raises
|
|
342
|
-
`ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS`, not tolerable either.
|
|
360
|
+
`ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS`, not tolerable either. Where it is a **repeat** boundary
|
|
361
|
+
nothing shifts, but the field is read out of its first repeat alone, so a gained first boundary
|
|
362
|
+
truncates a value and costs a test identity or a patient name the components that sat after it:
|
|
363
|
+
that raises `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD`, not tolerable either.
|
|
343
364
|
|
|
344
365
|
All are accepted limits, for two different reasons: widening the field-separator check would mean
|
|
345
366
|
deciding which set a record ought to have had, which is the same guess the parser declines to make
|
package/dist/index.cjs
CHANGED
|
@@ -329,6 +329,73 @@ var WARNING_CODES = {
|
|
|
329
329
|
* silent and is correct about its own bytes. A clean re-read is not evidence.
|
|
330
330
|
*/
|
|
331
331
|
ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS: "ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS",
|
|
332
|
+
/**
|
|
333
|
+
* Two escape alignments of the same bytes disagreed about a **repeat** boundary, the reading
|
|
334
|
+
* taken kept it, and the escape character that reading resumes on **heads no sequence at all**.
|
|
335
|
+
* Same contested position and same tail test as
|
|
336
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS}, on a different delimiter role, and
|
|
337
|
+
* what it costs is a different thing, which is why it is a different code.
|
|
338
|
+
*
|
|
339
|
+
* **Nothing shifts, and that is not the same as nothing being lost.** No field-indexed slot
|
|
340
|
+
* moves: the units and the result status are read out of the same field numbers under either
|
|
341
|
+
* alignment. What this reports is that **the field is read as more repeats than the competing
|
|
342
|
+
* alignment gives it**, which is true wherever it fires. What that costs depends on **which**
|
|
343
|
+
* boundary was gained, and the two cases are stated separately rather than folded into one claim.
|
|
344
|
+
*
|
|
345
|
+
* **Where it is the FIRST boundary in the field, a modeled slot is lost**, because a field's
|
|
346
|
+
* modeled value and components are taken from its **first repeat alone**. Everything past the
|
|
347
|
+
* boundary stays on the wire and stays in `repeats`, and leaves every modeled slot:
|
|
348
|
+
*
|
|
349
|
+
* - **A value truncates.** Under `H|F^&`, `R|1|^^^687|28.6&S&F&U/L||||F` reads the value field as
|
|
350
|
+
* the two repeats `28.6^` and `&U/L`, so a consumer reads `28.6^` and the rest of the value is
|
|
351
|
+
* gone. The competing alignment reads one repeat carrying all of it.
|
|
352
|
+
* - **A modeled component list is deleted rather than shifted.** The same boundary inside a
|
|
353
|
+
* Universal Test ID leaves the components of the first repeat only: under `H|F^&`,
|
|
354
|
+
* `R|1|&F&F&687|28.6|U/L||||F` reads a UTID of one component holding a decoded field separator,
|
|
355
|
+
* with no coding scheme and no local code, where the bytes carry `687`. A patient name loses
|
|
356
|
+
* its given and middle names the same way. That is the half of this condition the shift report
|
|
357
|
+
* could not cover, because components are modeled **inside** a field.
|
|
358
|
+
*
|
|
359
|
+
* Before this code the only warnings on those streams were **tolerable** ones
|
|
360
|
+
* ({@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS} and, where the tail applies,
|
|
361
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}), so the widest gate-legal profile plus
|
|
362
|
+
* `{ strict: true }` accepted a truncated value and an emptied Universal Test ID.
|
|
363
|
+
*
|
|
364
|
+
* **Where it is a LATER boundary, no modeled slot moves and this still fires.** The first repeat
|
|
365
|
+
* is identical under both alignments, so the value, the components and every slot taken from them
|
|
366
|
+
* read the same either way (`R|1|^^^687|5.0F28.6&S&F&U/L||||F` under `H|F^&` reads a value of
|
|
367
|
+
* `5.0` under both), and what differs is the repeat structure after the first. It fires there
|
|
368
|
+
* deliberately: the boundary is still one the bytes do not force and a consumer reading `repeats`
|
|
369
|
+
* is still reading an alignment guess. Relative to the modeled slots that is **over-reporting,
|
|
370
|
+
* never under-reporting**. Narrowing it to the first boundary would change which streams a
|
|
371
|
+
* published package refuses and wants its own measurement, so the bound is written down instead.
|
|
372
|
+
*
|
|
373
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical,
|
|
374
|
+
* `repeats` still carries every one of them, and the value read is the value that was always
|
|
375
|
+
* read. Picking the other alignment would be a different guess with no more evidence behind it,
|
|
376
|
+
* on a published package.
|
|
377
|
+
*
|
|
378
|
+
* **Two further deliberate bounds, both stated rather than left to be found.**
|
|
379
|
+
* - **Only the repeat role.** The **component** role reaches a modeled slot too, and differently:
|
|
380
|
+
* there the components stay in the record and move one slot along, so a local code reads as a
|
|
381
|
+
* coding scheme and a given name as a middle name. That is measured, reported by nothing where
|
|
382
|
+
* the earlier body is a recognized mnemonic, and open. It is not covered here, because wiring a
|
|
383
|
+
* sink to another split is another criterion wanting its own population measurement.
|
|
384
|
+
* - **The tail is weighed one construct deep**, exactly as for the shift report. Where the escape
|
|
385
|
+
* character the reading resumes on heads a sequence this codec *recognizes*, the bytes prefer
|
|
386
|
+
* the reading taken and the repeats are the ones the sender wrote: under a set naming the
|
|
387
|
+
* repeat separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again,
|
|
388
|
+
* and refusing it would be an over-refusal of a well-formed stream. Where it heads a sequence
|
|
389
|
+
* whose body is *unrecognized*, the competing alignment would leave **two** escape characters
|
|
390
|
+
* bare, so the preference is stronger again, and this stays silent there even though the gained
|
|
391
|
+
* boundary is real. That last case is the named residue: measured, not overlooked.
|
|
392
|
+
*
|
|
393
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized
|
|
394
|
+
* mnemonics, and those bytes carry the reading that was taken unambiguously, so a
|
|
395
|
+
* second-generation read is silent and is correct about its own bytes. A clean re-read is not
|
|
396
|
+
* evidence.
|
|
397
|
+
*/
|
|
398
|
+
ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD: "ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD",
|
|
332
399
|
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
333
400
|
ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
334
401
|
/**
|
|
@@ -488,6 +555,13 @@ function alignmentShiftedFields(position) {
|
|
|
488
555
|
position
|
|
489
556
|
};
|
|
490
557
|
}
|
|
558
|
+
function alignmentTruncatedField(position) {
|
|
559
|
+
return {
|
|
560
|
+
code: WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD,
|
|
561
|
+
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.",
|
|
562
|
+
position
|
|
563
|
+
};
|
|
564
|
+
}
|
|
491
565
|
function delimitersRedeclared(position) {
|
|
492
566
|
return {
|
|
493
567
|
code: WARNING_CODES.ASTM_RECORD_DELIMITERS_REDECLARED,
|
|
@@ -1113,7 +1187,7 @@ var referenceCorpus = defineAstmProfile({
|
|
|
1113
1187
|
tolerate: [
|
|
1114
1188
|
{
|
|
1115
1189
|
code: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
1116
|
-
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."
|
|
1190
|
+
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."
|
|
1117
1191
|
}
|
|
1118
1192
|
]
|
|
1119
1193
|
});
|
|
@@ -1187,7 +1261,7 @@ function isMnemonicBody(body) {
|
|
|
1187
1261
|
function escapeBody(body, d) {
|
|
1188
1262
|
return isMnemonicBody(body) ? d[MNEMONIC_ROLES[body]] : void 0;
|
|
1189
1263
|
}
|
|
1190
|
-
function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShiftedFields) {
|
|
1264
|
+
function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShiftedFields, onTruncatedField) {
|
|
1191
1265
|
if (text.length === 0) return [""];
|
|
1192
1266
|
const out = [];
|
|
1193
1267
|
let current = "";
|
|
@@ -1201,6 +1275,7 @@ function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShift
|
|
|
1201
1275
|
}
|
|
1202
1276
|
if (contested && !isEscapeSequenceAt(text, i + 4, escape)) {
|
|
1203
1277
|
onShiftedFields?.(out.length);
|
|
1278
|
+
onTruncatedField?.(out.length);
|
|
1204
1279
|
}
|
|
1205
1280
|
current += text.slice(i, i + 3);
|
|
1206
1281
|
i += 3;
|
|
@@ -1220,7 +1295,7 @@ function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShift
|
|
|
1220
1295
|
}
|
|
1221
1296
|
|
|
1222
1297
|
// src/records/tokenize.ts
|
|
1223
|
-
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields) {
|
|
1298
|
+
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1224
1299
|
const rawFields = splitEscapeAware(
|
|
1225
1300
|
record,
|
|
1226
1301
|
d.field,
|
|
@@ -1235,11 +1310,15 @@ function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowe
|
|
|
1235
1310
|
() => onUnknownEscape?.(fieldIndex),
|
|
1236
1311
|
() => onUnpairedEscape?.(fieldIndex),
|
|
1237
1312
|
() => onSwallowedDelimiter?.(fieldIndex),
|
|
1238
|
-
() => onAmbiguousAlignment?.(fieldIndex)
|
|
1313
|
+
() => onAmbiguousAlignment?.(fieldIndex),
|
|
1314
|
+
// The repeat split runs INSIDE one field, so the index it hands back is a repeat index and is
|
|
1315
|
+
// discarded: what a caller needs is which field carries the contested boundary, and that is
|
|
1316
|
+
// the index this map is already iterating.
|
|
1317
|
+
() => onAlignmentTruncatedField?.(fieldIndex)
|
|
1239
1318
|
)
|
|
1240
1319
|
);
|
|
1241
1320
|
}
|
|
1242
|
-
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields) {
|
|
1321
|
+
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1243
1322
|
const defEnd = record.indexOf(d.field, 2);
|
|
1244
1323
|
const definition = defEnd === -1 ? record.slice(2) : record.slice(2, defEnd);
|
|
1245
1324
|
const head = [opaqueField(record.slice(0, Math.min(1, record.length))), opaqueField(definition)];
|
|
@@ -1251,17 +1330,27 @@ function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowe
|
|
|
1251
1330
|
(i) => onUnpairedEscape?.(i + 2),
|
|
1252
1331
|
(i) => onSwallowedDelimiter?.(i + 2),
|
|
1253
1332
|
(i) => onAmbiguousAlignment?.(i + 2),
|
|
1254
|
-
(i) => onAlignmentShiftedFields?.(i + 2)
|
|
1333
|
+
(i) => onAlignmentShiftedFields?.(i + 2),
|
|
1334
|
+
(i) => onAlignmentTruncatedField?.(i + 2)
|
|
1255
1335
|
);
|
|
1256
1336
|
return [...head, ...data];
|
|
1257
1337
|
}
|
|
1258
1338
|
function opaqueField(raw) {
|
|
1259
1339
|
return { raw, components: [raw], repeats: [[raw]] };
|
|
1260
1340
|
}
|
|
1261
|
-
function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment) {
|
|
1262
|
-
const rawRepeats = splitEscapeAware(
|
|
1263
|
-
|
|
1264
|
-
|
|
1341
|
+
function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentTruncatedField) {
|
|
1342
|
+
const rawRepeats = splitEscapeAware(
|
|
1343
|
+
raw,
|
|
1344
|
+
d.repeat,
|
|
1345
|
+
d.escape,
|
|
1346
|
+
() => {
|
|
1347
|
+
onAmbiguousAlignment();
|
|
1348
|
+
},
|
|
1349
|
+
void 0,
|
|
1350
|
+
() => {
|
|
1351
|
+
onAlignmentTruncatedField();
|
|
1352
|
+
}
|
|
1353
|
+
);
|
|
1265
1354
|
const repeats = rawRepeats.map(
|
|
1266
1355
|
(rep) => splitEscapeAware(rep, d.component, d.escape, () => {
|
|
1267
1356
|
onAmbiguousAlignment();
|
|
@@ -1412,6 +1501,11 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1412
1501
|
alignmentShiftedFields({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1413
1502
|
);
|
|
1414
1503
|
};
|
|
1504
|
+
const onAlignmentTruncatedField = (fieldIndex) => {
|
|
1505
|
+
warnings.push(
|
|
1506
|
+
alignmentTruncatedField({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1507
|
+
);
|
|
1508
|
+
};
|
|
1415
1509
|
const fields = rawType === "H" ? tokenizeHeader(
|
|
1416
1510
|
line2,
|
|
1417
1511
|
d,
|
|
@@ -1419,7 +1513,8 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1419
1513
|
onUnpairedEscape,
|
|
1420
1514
|
onSwallowedDelimiter,
|
|
1421
1515
|
onAmbiguousAlignment,
|
|
1422
|
-
onAlignmentShiftedFields
|
|
1516
|
+
onAlignmentShiftedFields,
|
|
1517
|
+
onAlignmentTruncatedField
|
|
1423
1518
|
) : tokenizeRecord(
|
|
1424
1519
|
line2,
|
|
1425
1520
|
d,
|
|
@@ -1427,7 +1522,8 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1427
1522
|
onUnpairedEscape,
|
|
1428
1523
|
onSwallowedDelimiter,
|
|
1429
1524
|
onAmbiguousAlignment,
|
|
1430
|
-
onAlignmentShiftedFields
|
|
1525
|
+
onAlignmentShiftedFields,
|
|
1526
|
+
onAlignmentTruncatedField
|
|
1431
1527
|
);
|
|
1432
1528
|
if (fields.length === 1 && line2.slice(1).trim().length > 0) {
|
|
1433
1529
|
warnings.push(fieldsUnseparated({ recordIndex, recordType: rawType }));
|
|
@@ -2640,7 +2736,7 @@ function concatBytes3(chunks) {
|
|
|
2640
2736
|
}
|
|
2641
2737
|
|
|
2642
2738
|
// src/index.ts
|
|
2643
|
-
var VERSION = "0.0.
|
|
2739
|
+
var VERSION = "0.0.18";
|
|
2644
2740
|
|
|
2645
2741
|
exports.ALL_ASTM_WARNING_CODES = ALL_ASTM_WARNING_CODES;
|
|
2646
2742
|
exports.AMBIGUOUS_CODES = AMBIGUOUS_CODES;
|
|
@@ -2665,6 +2761,7 @@ exports.TOLERABLE_CODES = TOLERABLE_CODES;
|
|
|
2665
2761
|
exports.VERSION = VERSION;
|
|
2666
2762
|
exports.WARNING_CODES = WARNING_CODES;
|
|
2667
2763
|
exports.alignmentShiftedFields = alignmentShiftedFields;
|
|
2764
|
+
exports.alignmentTruncatedField = alignmentTruncatedField;
|
|
2668
2765
|
exports.ambiguousEscapeAlignment = ambiguousEscapeAlignment;
|
|
2669
2766
|
exports.ambiguousMessageKind = ambiguousMessageKind;
|
|
2670
2767
|
exports.ambiguousValueSplit = ambiguousValueSplit;
|