@domorium/validator 2.0.0 → 2.1.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/CHANGELOG.md +38 -0
- package/README.md +119 -2
- package/dist/document/gedcomDocument.d.ts +9 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +348 -225
- package/dist/types/date.d.ts +21 -0
- package/dist/validator/date-551.d.ts +10 -0
- package/dist/validator/date-v7.d.ts +2 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@domorium/validator` are documented here.
|
|
4
4
|
|
|
5
|
+
## 2.1.0
|
|
6
|
+
|
|
7
|
+
- **`readDate(payload)` answers what a date says, not only whether it is
|
|
8
|
+
valid.** A consumer wanting the year of a birth had to parse the payload
|
|
9
|
+
again, and a regular expression that finds `1401` in `1401 BCE` or a year in
|
|
10
|
+
`ABT HEBREW 5761` hands on a wrong year as a fact. The document now returns
|
|
11
|
+
the parse its diagnostics apply: the modifier, each date with its calendar,
|
|
12
|
+
day, month, year and epoch, a 5.5.1 phrase and dual year, and the text. It
|
|
13
|
+
reads in the document's dialect, resolves a `SCHMA` alias of a month or a
|
|
14
|
+
calendar, and answers `null` for whatever the diagnostics reject as a date
|
|
15
|
+
value, including a day its month does not have (#378).
|
|
16
|
+
|
|
17
|
+
## 2.0.1
|
|
18
|
+
|
|
19
|
+
- **A 5.5.1 date names its calendar where the specification puts it.** 5.5.1
|
|
20
|
+
writes the escape inside `<DATE>`, and `<DATE>` comes after the modifier:
|
|
21
|
+
`ABT <DATE>`, `BET <DATE> AND <DATE>`, `FROM <DATE> TO <DATE>`. The escape was
|
|
22
|
+
read only at the front of the whole payload, so `ABT @#DHEBREW@ TSH 5760` and
|
|
23
|
+
a Julian `FROM`/`TO` range were reported as invalid dates, while
|
|
24
|
+
`@#DJULIAN@ FROM 1 JAN 1700 TO 1 JAN 1710`, which the grammar does not admit,
|
|
25
|
+
passed. Each `<DATE>` now carries its own calendar and is read in
|
|
26
|
+
that calendar's months, so a range may cross calendars and neither half is
|
|
27
|
+
read in the other's vocabulary.
|
|
28
|
+
- **An exact date is Gregorian, and says so.** `DATE_EXACT` is
|
|
29
|
+
`<DAY> <MONTH> <YEAR_GREG>` with no slot for a calendar, but the escape was
|
|
30
|
+
stripped before the grammar ran, so `1 CHAN` / `2 DATE @#DHEBREW@ 1 TSH 5760`
|
|
31
|
+
validated clean — where the GEDCOM 7 rule for the same payload rejects it. The
|
|
32
|
+
two dialects agreed on every other date rule and disagreed on this one.
|
|
33
|
+
- **A day is measured against the calendar its own date names.** A calendar binds
|
|
34
|
+
to the date that follows it, which is what `FROM JULIAN 1670 TO 1800` says: two
|
|
35
|
+
dates, two calendars. The day-length check set the calendar once and carried it
|
|
36
|
+
to the end of the payload, so `FROM JULIAN 1 JAN 1700 TO 29 FEB 1900` reported
|
|
37
|
+
nothing — the second date is Gregorian, 1900 is not a Gregorian leap year, and
|
|
38
|
+
it was measured by the Julian rule, which has no exception for a century. The
|
|
39
|
+
same leading calendar suppressed the check for every later date in a range.
|
|
40
|
+
Each word that ends one date and begins the next now returns the calendar to
|
|
41
|
+
Gregorian, so a date names its own or is read as Gregorian.
|
|
42
|
+
|
|
5
43
|
## 2.0.0 - 2026-08-20
|
|
6
44
|
|
|
7
45
|
Removes three members of `GedcomDocument` that no consumer read, which is what
|
package/README.md
CHANGED
|
@@ -59,8 +59,58 @@ interface GedcomError {
|
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
| Field | Type | What it holds |
|
|
63
|
+
| --------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `code` | `string`, a `GedcomErrorCode` | What kind of problem this is. Match on this. |
|
|
65
|
+
| `message` | `string` | Written for a person to read, and rewritten when a clearer wording is found. Do not match on it. |
|
|
66
|
+
| `hint` | `string?` | Declared for advice alongside the message. Nothing in this package sets it yet, so treat it as absent. |
|
|
67
|
+
| `data` | object? | The structured facts a quick fix is built from, where the diagnostic has any. |
|
|
68
|
+
| `range` | `Range` | Zero-based line and character, `end` exclusive, over the smallest text that is wrong — the payload, not the line. |
|
|
69
|
+
| `level` | `"error" \| "warning" \| "info"` | How much it matters. |
|
|
70
|
+
|
|
71
|
+
`data` is what makes a fix offerable rather than merely reportable: an
|
|
72
|
+
unresolved pointer reports the xref that failed and the record tag it needed, so
|
|
73
|
+
a host can offer to create that record.
|
|
74
|
+
|
|
75
|
+
For this file, where `SEX` carries a value its schema does not permit and `FAMC`
|
|
76
|
+
points at a record that is not there:
|
|
77
|
+
|
|
78
|
+
```gedcom
|
|
79
|
+
0 HEAD
|
|
80
|
+
1 GEDC
|
|
81
|
+
2 VERS 7.0
|
|
82
|
+
0 @I1@ INDI
|
|
83
|
+
1 NAME Lisa /Simpson/
|
|
84
|
+
1 SEX Q
|
|
85
|
+
1 FAMC @F9@
|
|
86
|
+
0 TRLR
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`getErrors()` answers:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
"code": "VAL005",
|
|
95
|
+
"message": "Value for SEX should be in set [F, M, U, X]",
|
|
96
|
+
"range": {
|
|
97
|
+
"start": { "line": 5, "character": 6 },
|
|
98
|
+
"end": { "line": 5, "character": 7 }
|
|
99
|
+
},
|
|
100
|
+
"level": "error"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"code": "unresolved-xref",
|
|
104
|
+
"message": "No FAM record carries @F9@",
|
|
105
|
+
"data": { "xref": "@F9@", "requiredRecordTag": "FAM" },
|
|
106
|
+
"range": {
|
|
107
|
+
"start": { "line": 6, "character": 7 },
|
|
108
|
+
"end": { "line": 6, "character": 11 }
|
|
109
|
+
},
|
|
110
|
+
"level": "error"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
```
|
|
64
114
|
|
|
65
115
|
Match on `code` rather than on `message`; messages are written for people and
|
|
66
116
|
change. `GedcomErrorCode` is exported for this:
|
|
@@ -95,6 +145,73 @@ const unresolved = errors.filter(
|
|
|
95
145
|
| `LEXER` | `Lexer` | the text could not be tokenized |
|
|
96
146
|
| `PARSER` | `Parser` | the tokens could not be assembled into a tree |
|
|
97
147
|
|
|
148
|
+
## Reading the tree
|
|
149
|
+
|
|
150
|
+
`getNodes()` returns the records — the level-0 nodes — each holding its
|
|
151
|
+
substructures in `children`. A node carries its line's tokens in a `tokens` map
|
|
152
|
+
keyed by `TokenNames`, and which key holds the text is the thing to get right:
|
|
153
|
+
|
|
154
|
+
| Token | Written as | Holds |
|
|
155
|
+
| --------- | ------------- | -------------------------------------- |
|
|
156
|
+
| `LEVEL` | `1` | The level, as it was written. |
|
|
157
|
+
| `POINTER` | `0 @I1@ INDI` | The xref a record declares for itself. |
|
|
158
|
+
| `TAG` | `1 NAME` | The tag. |
|
|
159
|
+
| `XREF` | `1 FAMC @F1@` | An xref a line points at. |
|
|
160
|
+
| `VALUE` | `1 NAME Lisa` | A payload that is not a pointer. |
|
|
161
|
+
|
|
162
|
+
`POINTER` and `XREF` are the pair worth reading twice: a record's own xref is
|
|
163
|
+
never in `XREF`, and a pointer payload is never in `VALUE`.
|
|
164
|
+
|
|
165
|
+
Pulling every person's name out of a file:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { GedcomDocument, TokenNames } from "@domorium/validator";
|
|
169
|
+
import type { ASTNode } from "@domorium/validator";
|
|
170
|
+
|
|
171
|
+
const document = new GedcomDocument().createDocument(gedcomString);
|
|
172
|
+
|
|
173
|
+
const payload = (node: ASTNode, tag: string): string | undefined =>
|
|
174
|
+
node.children.find((child) => child.tokens[TokenNames.TAG]?.value === tag)
|
|
175
|
+
?.tokens[TokenNames.VALUE]?.value;
|
|
176
|
+
|
|
177
|
+
for (const record of document.getNodes()) {
|
|
178
|
+
if (record.tokens[TokenNames.TAG]?.value !== "INDI") continue;
|
|
179
|
+
console.log({
|
|
180
|
+
xref: record.tokens[TokenNames.POINTER]?.value,
|
|
181
|
+
name: payload(record, "NAME"),
|
|
182
|
+
line: record.range.start.line,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
{ xref: '@I1@', name: 'Lisa /Simpson/', line: 3 }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`range` on a node or a token is computed from the offsets on every access, not
|
|
192
|
+
stored: a large file has millions of tokens and a stored range costs three
|
|
193
|
+
objects apiece. Read it freely at the edges, and use `startOffset` and
|
|
194
|
+
`endOffset` inside a loop over the whole document.
|
|
195
|
+
|
|
196
|
+
## What a document answers
|
|
197
|
+
|
|
198
|
+
Every method reads the document last given to `createDocument`, which returns
|
|
199
|
+
the same instance so the two can be chained.
|
|
200
|
+
|
|
201
|
+
| Method | Answers |
|
|
202
|
+
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
203
|
+
| `createDocument(text, options?)` | Parses and validates, and returns the document itself. |
|
|
204
|
+
| `getErrors()` | Every diagnostic, in the order found. |
|
|
205
|
+
| `getNodes()` | The records, each with its substructures. |
|
|
206
|
+
| `getVersion()` | The version read from `HEAD`.`GEDC`.`VERS`, as written. |
|
|
207
|
+
| `getDialect()` | Which rules were applied: `"5.5.1"` or `"7.0"`. |
|
|
208
|
+
| `getVersionResolution()` | How that was decided — `supported`, `substituted`, `unsupported`, `undetermined`, or `paf` for a file whose header names a writing program before its version. |
|
|
209
|
+
| `getLabel(node)` | The specification's name for a structure, for a UI to show instead of the tag: `HEAD` answers `Header`. |
|
|
210
|
+
| `getPointerTargetTag(node)` | The record tag a pointer line must name: `FAMC` answers `FAM`. |
|
|
211
|
+
| `isRecordDeclaration(node)` | Whether the node declares a record other lines can point at. |
|
|
212
|
+
| `getCompletions(position, lineText)` | What may be written at a position — `{ label, kind: "tag" \| "enum" \| "pointer", detail? }`. |
|
|
213
|
+
| `readDate(payload)` | What a date payload says, read in the document's dialect — or `null` where the diagnostics would reject it as a date value. See `GedcomDateValue`. |
|
|
214
|
+
|
|
98
215
|
## Scripts
|
|
99
216
|
|
|
100
217
|
| Command | Description |
|
|
@@ -3,6 +3,7 @@ import { ASTNode } from "../parser";
|
|
|
3
3
|
import { type GedcomDialect, type VersionResolution } from "../validator/versionRegistry";
|
|
4
4
|
import { type GedcomCompletion } from "../completion/completion";
|
|
5
5
|
import type { Position } from "../types/position";
|
|
6
|
+
import type { GedcomDateValue } from "../types/date";
|
|
6
7
|
export interface CreateDocumentOptions {
|
|
7
8
|
/** Text that is part of a document rather than one of its own. */
|
|
8
9
|
fragment?: boolean;
|
|
@@ -14,6 +15,7 @@ export declare class GedcomDocument {
|
|
|
14
15
|
private pointers;
|
|
15
16
|
private errors;
|
|
16
17
|
private scheme;
|
|
18
|
+
private validatedAs;
|
|
17
19
|
private resolution;
|
|
18
20
|
private requiresSchmaDeclaration;
|
|
19
21
|
private extensions;
|
|
@@ -28,6 +30,13 @@ export declare class GedcomDocument {
|
|
|
28
30
|
getVersionResolution(): VersionResolution | undefined;
|
|
29
31
|
getDialect(): GedcomDialect | undefined;
|
|
30
32
|
getCompletions(position: Position, lineText: string): GedcomCompletion[];
|
|
33
|
+
/**
|
|
34
|
+
* Null wherever the document's own diagnostics would not call the payload a
|
|
35
|
+
* valid date value, and for one with nothing to read: an empty payload, a
|
|
36
|
+
* 5.5.1 date in a calendar the schema gives no months, or a document not
|
|
37
|
+
* checked against any dialect.
|
|
38
|
+
*/
|
|
39
|
+
readDate(value: string): GedcomDateValue | null;
|
|
31
40
|
getErrors(): GedcomError[];
|
|
32
41
|
getNodes(): ASTNode[];
|
|
33
42
|
}
|