gedcomkit 0.1.17__tar.gz → 0.1.18__tar.gz
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.
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/CHANGELOG.md +126 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/Cargo.lock +1 -1
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/Cargo.toml +1 -1
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/PKG-INFO +1 -1
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/README.md +3 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/bindings/gedcomkit-py/Cargo.lock +2 -2
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/bindings/gedcomkit-py/Cargo.toml +1 -1
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/bindings/gedcomkit-py/gedcomkit.pyi +5 -1
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/bindings/gedcomkit-py/src/lib.rs +4 -1
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/dates.rs +134 -7
- gedcomkit-0.1.18/src/validate.rs +3470 -0
- gedcomkit-0.1.17/src/validate.rs +0 -855
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/LICENSE +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/bindings/gedcomkit-py/LICENSE +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/bindings/gedcomkit-py/README.md +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/LICENSE +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/README.md +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/cardinalities.tsv +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/enumerations.tsv +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/enumerationsets.tsv +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/grammar.abnf +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/grammar.gedstruct +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/payloads.tsv +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom-spec/substructures.tsv +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/age-invalid.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/age-valid.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/atsign.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/date-all.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/date-dual-valid.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/enum-ext.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/filename-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/lang-all.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/notes-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/obje-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/obsolete-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/pedi-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/rela_1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/sour-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/tiny-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/5/xref-case.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/age-invalid.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/age-valid.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/atsign.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/date-all.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/enum-ext.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/filename-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/lang-all.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/notes-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/obje-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/obsolete-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/pedi-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/rela_1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/sour-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/tiny-1.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/7/xref-case.ged +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/LICENSE +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/gedcom7code/UPSTREAM-README.md +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/zip-interop/README.md +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/zip-interop/python-zip64.gdz +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/fixtures/vendored/zip-interop/python-zipfile.gdz +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/pyproject.toml +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/build.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/convert.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/decode.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/family.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/gedzip.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/lib.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/names.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/schema.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/synthetic.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/tags.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/src/view.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/endurance.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/external_charset.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/properties.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/requirement_corpus.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/spec_conformance.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/typescript.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/vendored_corpus.rs +0 -0
- {gedcomkit-0.1.17 → gedcomkit-0.1.18}/tests/version_conversion.rs +0 -0
|
@@ -5,6 +5,132 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
5
5
|
the project uses semantic versioning within the usual pre-1.0 compatibility
|
|
6
6
|
rules.
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.18] - 2026-10-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `validate::inspect` runs nine more checks. Each has a stable name, and
|
|
15
|
+
the module documentation now lists every check with its severity:
|
|
16
|
+
- `married-after-died` (contradiction): a family's first `MARR` wholly
|
|
17
|
+
after a partner's death, or, failing a dated death, their burial. A
|
|
18
|
+
burial before the wedding beside a death after it is no finding here:
|
|
19
|
+
the death says when the life ended, and the burial is
|
|
20
|
+
`buried-before-died`'s.
|
|
21
|
+
- `child-born-long-after-father-died`: a child born more than 300 days
|
|
22
|
+
after the death of the family's `HUSB`. A contradiction when both dates
|
|
23
|
+
name a day; a question otherwise, since a bare year is often a guess.
|
|
24
|
+
- `christened-before-born` (contradiction): a `CHR`, `CHRA` or `BAPM`
|
|
25
|
+
wholly before the birth.
|
|
26
|
+
- `event-before-birth`: the mirror of `event-after-death`, for any other
|
|
27
|
+
event or attribute wholly before the birth year. A burial or cremation
|
|
28
|
+
is left to `died-before-born` and `buried-before-died` when the person
|
|
29
|
+
has a dated death.
|
|
30
|
+
- `buried-long-after-died` (question): a burial more than 365 days after
|
|
31
|
+
the death, counted from the latest day the death allows to the earliest
|
|
32
|
+
the burial does.
|
|
33
|
+
- `children-too-close`: two children of one mother born 2 to 270 days
|
|
34
|
+
apart. A contradiction when both dates name a day and are under 150
|
|
35
|
+
days apart; a question otherwise, since a very early birth makes the
|
|
36
|
+
longer gaps possible. A mother's children are taken from every family
|
|
37
|
+
she is the `WIFE` in; a family with no `WIFE` is compared alone.
|
|
38
|
+
- `possible-duplicate-child` (question): two children of one mother with
|
|
39
|
+
one given name (one name may be the start of the other: `Mira` and
|
|
40
|
+
`Mira Elowen`, but not `Mira Elowen` and `Mira Rose`), born on one
|
|
41
|
+
date, or on one day and month of different years.
|
|
42
|
+
- `conflicting-vitals` (question): two `BIRT`, or two `DEAT`, events of
|
|
43
|
+
one person that cannot both be true, even with an approximate date
|
|
44
|
+
allowed a year either way (`BIRT 1851` and `BIRT ABT 1850` are not in
|
|
45
|
+
conflict). GEDCOM lets a file record alternatives with the preferred
|
|
46
|
+
first, which is the one the other checks read, so this asks rather
|
|
47
|
+
than accuses.
|
|
48
|
+
- `own-ancestor` (contradiction): a loop in the parent links, read from
|
|
49
|
+
both directions of the family link as `FamilyGraph` reads them. One
|
|
50
|
+
finding per loop, on the person in it the file lists first, naming the
|
|
51
|
+
shortest way round: in full up to six steps, and past that its first
|
|
52
|
+
five and how many more, so a loop through 20,000 people is one short
|
|
53
|
+
message.
|
|
54
|
+
|
|
55
|
+
The three checks that compare pairs are bounded, so a hostile file cannot
|
|
56
|
+
make the report as large as the square of its people. `children-too-close`
|
|
57
|
+
and `possible-duplicate-child` report at most ten pairs for one mother,
|
|
58
|
+
and `conflicting-vitals` ten for one person's births and ten for their
|
|
59
|
+
deaths, the first in the order the file lists them; one more finding under
|
|
60
|
+
the same check, as serious as the most serious pair it stands for, then
|
|
61
|
+
says how many more pairs there are and how many in all (`4059990 more
|
|
62
|
+
pairs of children born 2 to 270 days apart, 4060000 in all`). The work is
|
|
63
|
+
bounded too: births and deaths are counted by sorting rather than compared
|
|
64
|
+
pair by pair; children who share a span of birth days, or a given name,
|
|
65
|
+
are compared as one group; and one mother's children are compared at most
|
|
66
|
+
100,000 times for each check, past which the summary gives its counts as
|
|
67
|
+
lower bounds. A family of 3,000 children of one name born in one month,
|
|
68
|
+
which made 4.5 million sibling findings, makes 22; a person with 5,000
|
|
69
|
+
births, which made 12.5 million `conflicting-vitals` findings, makes 11.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- PyPI gets one wheel, Linux x86_64, beside the source distribution. The
|
|
74
|
+
macOS and Windows wheels are no longer cross-built; on those platforms pip
|
|
75
|
+
builds from the sdist, which needs a Rust toolchain.
|
|
76
|
+
- `died-before-born`, `buried-before-died`, `christened-before-born`,
|
|
77
|
+
`married-after-died` and `conflicting-vitals` compare as finely as both
|
|
78
|
+
dates allow: a burial on 12 September before a death on 14 September of
|
|
79
|
+
one year is now found. Julian and Gregorian dates are put on one count of days first.
|
|
80
|
+
An approximate date (`ABT`, `CAL`, `EST`) still vouches for its year and
|
|
81
|
+
nothing finer, and a bound (`BEF`, `AFT`, `BET`) admits its own day. A
|
|
82
|
+
finding the years alone decide keeps its wording (`born 1900 and died
|
|
83
|
+
1880`); one only a finer comparison finds names the dates in full
|
|
84
|
+
(`died 14 September 1862 and buried 12 September 1862`).
|
|
85
|
+
- An approximate date (`ABT`, `CAL`, `EST`) may be a year out. A finding
|
|
86
|
+
that two events are out of order is a question, not a contradiction, when
|
|
87
|
+
moving each approximate date a year either way could put them in order:
|
|
88
|
+
`BIRT ABT 1850` with `CHR 1849` or `CENS 1849`, or `DEAT ABT 1850` with a
|
|
89
|
+
burial in 1849. Two years out it is still a contradiction. This holds for
|
|
90
|
+
`died-before-born`, `buried-before-died`, `event-after-death`,
|
|
91
|
+
`married-before-born` and `child-born-after-mother-died`, and for the new
|
|
92
|
+
`christened-before-born`, `event-before-birth` and `married-after-died`;
|
|
93
|
+
the checks about an age or an interval take an approximate year as
|
|
94
|
+
written. Their messages say an approximate date is one: `born about 1850,
|
|
95
|
+
and Christening is dated 1849`.
|
|
96
|
+
- `event-after-death` is a question, not a contradiction, for an `EVEN` or
|
|
97
|
+
`FACT`. Such an event is whatever its free-text `TYPE` says — a land
|
|
98
|
+
entry completed, a patent issued, an estate settled — and many of those
|
|
99
|
+
are done in a dead person's name. The other events keep their severity.
|
|
100
|
+
- `by` is read as a `BEF` qualifier: `by 5 October 1752` is
|
|
101
|
+
`BEF 5 OCT 1752`, where it was read as 5 May 1752. The qualifiers the
|
|
102
|
+
reader already knew (`abt`, `about`, `c.`, `ca.`, `bef`, `before`, `aft`,
|
|
103
|
+
`after`, and the others) are unchanged.
|
|
104
|
+
|
|
105
|
+
### Fixed
|
|
106
|
+
|
|
107
|
+
- The date reader no longer shifts the pieces of a four-word date around a
|
|
108
|
+
word it does not know. `chr 10 Nov 1788` read as 10 October 1788,
|
|
109
|
+
`b 9 Jun 1750` as 9 September, `bapt. 3 May 1801` as 3 March: the leading
|
|
110
|
+
word was tried as a month, failed, and the day number was taken as the
|
|
111
|
+
month instead. Four pieces are now read only as a day, its ordinal ending
|
|
112
|
+
and a month written as a word, in either order (`Feb 11 th 1809`,
|
|
113
|
+
`11 th Feb 1809`, `1er janvier 1800`, `5te Januar 1800`); anything else is
|
|
114
|
+
`DateKind::Unparsed`, keeps its text as the phrase, and still offers its
|
|
115
|
+
year to sort by, so `to_gedcom_5_5_1` and `to_gedcom_7` return `None` and
|
|
116
|
+
the caller writes it as a date phrase. A month given only as a number never
|
|
117
|
+
decides the order of four pieces.
|
|
118
|
+
- A dual year (`10 JAN 1731/32`) admits both its years in every check, not
|
|
119
|
+
only where days are compared. The checks that compare years read it as
|
|
120
|
+
its first year alone, so `BIRT 1732` with `DEAT 10 JAN 1731/32` was
|
|
121
|
+
`died-before-born` ("born 1732 and died 1731"); it is now no finding, and
|
|
122
|
+
a death past both years says `died between 1731 and 1732`. Where the days
|
|
123
|
+
of two dates are compared, they alone decide the order, so a burial in the
|
|
124
|
+
Julian year 1700, which runs eleven days into the Gregorian 1701, is no
|
|
125
|
+
longer wholly before a death in the Gregorian 1701.
|
|
126
|
+
- `child-listed-twice` keeps the children it has seen in a set rather than
|
|
127
|
+
a list, so a family listing tens of thousands of children is checked in
|
|
128
|
+
time proportional to their number, not its square.
|
|
129
|
+
- The checks on an individual's own record read that record's dates when it
|
|
130
|
+
has no identifier, or shares one with another record. Before, such a
|
|
131
|
+
record was checked against no dates, or against those of the last record
|
|
132
|
+
with its identifier.
|
|
133
|
+
|
|
8
134
|
## [0.1.17] - 2026-10-16
|
|
9
135
|
|
|
10
136
|
### Changed
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# was checked on crates.io, npm, and PyPI before the initial release.
|
|
4
4
|
[package]
|
|
5
5
|
name = "gedcomkit"
|
|
6
|
-
version = "0.1.
|
|
6
|
+
version = "0.1.18"
|
|
7
7
|
description = "A byte-preserving GEDCOM document model: decoding, parsing, readings, version conversion, plausibility checks, and the GEDZIP container, for GEDCOM 5.5 through 7.x."
|
|
8
8
|
edition = "2024"
|
|
9
9
|
# The true minimum, not the workspace's toolchain: a parser library is a leaf
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "gedcomkit"
|
|
38
|
-
version = "0.1.
|
|
38
|
+
version = "0.1.18"
|
|
39
39
|
dependencies = [
|
|
40
40
|
"flate2",
|
|
41
41
|
"serde",
|
|
@@ -43,7 +43,7 @@ dependencies = [
|
|
|
43
43
|
|
|
44
44
|
[[package]]
|
|
45
45
|
name = "gedcomkit-py"
|
|
46
|
-
version = "0.1.
|
|
46
|
+
version = "0.1.18"
|
|
47
47
|
dependencies = [
|
|
48
48
|
"gedcomkit",
|
|
49
49
|
"pyo3",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# abi3 keeps it to one wheel per platform across Python versions.
|
|
3
3
|
[package]
|
|
4
4
|
name = "gedcomkit-py"
|
|
5
|
-
version = "0.1.
|
|
5
|
+
version = "0.1.18"
|
|
6
6
|
description = "Python bindings for gedcomkit: an archival-grade GEDCOM library — byte-preserving round-trip, encoding detection, GEDCOM 5.5 through 7.x, GEDZIP."
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.88"
|
|
@@ -44,7 +44,11 @@ class Gedcom:
|
|
|
44
44
|
def record(self, xref: str) -> dict[str, Any]: ...
|
|
45
45
|
def individuals(self) -> list[dict[str, Any]]: ...
|
|
46
46
|
def families(self) -> list[dict[str, Any]]: ...
|
|
47
|
-
def validate(self) -> list[dict[str, Any]]:
|
|
47
|
+
def validate(self) -> list[dict[str, Any]]:
|
|
48
|
+
"""Plausibility findings, each with ``record``, ``check``, ``severity``
|
|
49
|
+
and ``message``. ``check`` is the core's stable rule name, such as
|
|
50
|
+
``died-before-born`` or ``own-ancestor``; the core's ``validate``
|
|
51
|
+
module documentation lists every one with its severity."""
|
|
48
52
|
def unresolved_pointers(self) -> list[dict[str, Any]]: ...
|
|
49
53
|
def parents_of(self, xref: str) -> list[str]: ...
|
|
50
54
|
def children_of(self, xref: str) -> list[str]: ...
|
|
@@ -240,7 +240,10 @@ impl Gedcom {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/// Plausibility findings: contradictions, questions, and what cites
|
|
243
|
-
/// nothing — reported with the numbers they were drawn from.
|
|
243
|
+
/// nothing — reported with the numbers they were drawn from. Each has
|
|
244
|
+
/// `record`, `check`, `severity` and `message`; `check` is the core's
|
|
245
|
+
/// stable rule name (`died-before-born`, `own-ancestor`, …), listed with
|
|
246
|
+
/// its severity in the core's `validate` module documentation.
|
|
244
247
|
fn validate(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
|
|
245
248
|
gedcomkit::validate::inspect(&self.document)
|
|
246
249
|
.iter()
|
|
@@ -571,7 +571,7 @@ fn point_5_5_1(point: DatePoint) -> String {
|
|
|
571
571
|
/// The first slashed year pair in a payload — `1740/41`, `1679/1683` — as
|
|
572
572
|
/// two full years. `8/2/1664` is a numeric date, not a pair, and is skipped
|
|
573
573
|
/// because a pair's first half has three or four digits.
|
|
574
|
-
fn dual_year(text: &str) -> Option<(i32, i32)> {
|
|
574
|
+
pub(crate) fn dual_year(text: &str) -> Option<(i32, i32)> {
|
|
575
575
|
let bytes = text.as_bytes();
|
|
576
576
|
let mut index = 0;
|
|
577
577
|
while index < bytes.len() {
|
|
@@ -721,9 +721,14 @@ const PREFIXES: &[(DateKind, &[&str])] = &[
|
|
|
721
721
|
),
|
|
722
722
|
(DateKind::Calculated, &["CAL", "CALCULATED"]),
|
|
723
723
|
(DateKind::Estimated, &["EST", "ESTIMATED"]),
|
|
724
|
+
// `by 5 October 1752` says the event had happened by that day. GEDCOM
|
|
725
|
+
// has no "on or before", and `BEF` is the nearest claim; `validate`
|
|
726
|
+
// compares a bound as admitting the bound itself, so nothing is lost.
|
|
724
727
|
(
|
|
725
728
|
DateKind::Before,
|
|
726
|
-
&[
|
|
729
|
+
&[
|
|
730
|
+
"BEF", "BEFORE", "BY", "AVANT", "VOR", "PRIMA", "ANTES", "FØR",
|
|
731
|
+
],
|
|
727
732
|
),
|
|
728
733
|
(
|
|
729
734
|
DateKind::After,
|
|
@@ -930,20 +935,48 @@ fn parse_point(input: &str) -> Option<DatePoint> {
|
|
|
930
935
|
})
|
|
931
936
|
}
|
|
932
937
|
[first, second, third, fourth] => {
|
|
933
|
-
//
|
|
938
|
+
// Four pieces are a day with its ordinal ending beside a month
|
|
939
|
+
// written as a word: `Feb 11 th 1809` and `April 15th, 1873`
|
|
940
|
+
// month-first, `11 th Feb 1809` and `1er janvier 1800` day-first.
|
|
941
|
+
// Nothing else is read. A word the reader does not know in front
|
|
942
|
+
// of a date — `chr 10 Nov 1788`, `b 9 Jun 1750`, `bapt. 3 May
|
|
943
|
+
// 1801` — leaves the payload unparsed rather than shifting the
|
|
944
|
+
// pieces around it: guessing past such a word once took the day
|
|
945
|
+
// for the month and read `10 OCT 1788`.
|
|
934
946
|
let year = parse_year(fourth)?;
|
|
935
|
-
let month =
|
|
936
|
-
|
|
947
|
+
let (month, day) = if is_month_name(first) && is_ordinal_ending(third) {
|
|
948
|
+
(first, second)
|
|
949
|
+
} else if is_month_name(third) && is_ordinal_ending(second) {
|
|
950
|
+
(third, first)
|
|
951
|
+
} else {
|
|
952
|
+
return None;
|
|
953
|
+
};
|
|
937
954
|
Some(DatePoint {
|
|
938
955
|
year,
|
|
939
|
-
month: Some(month),
|
|
940
|
-
day: Some(day),
|
|
956
|
+
month: Some(month_number(month)?),
|
|
957
|
+
day: Some(parse_day(day)?),
|
|
941
958
|
})
|
|
942
959
|
}
|
|
943
960
|
_ => None,
|
|
944
961
|
}
|
|
945
962
|
}
|
|
946
963
|
|
|
964
|
+
/// Whether a token is a month written as a word, never as a number.
|
|
965
|
+
fn is_month_name(token: &str) -> bool {
|
|
966
|
+
!token.bytes().all(|byte| byte.is_ascii_digit()) && month_number(token).is_some()
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/// Whether a token is the ending of an ordinal day once the digits have been
|
|
970
|
+
/// split from it: English `11th` and `2nd`, French `1er`, Danish and
|
|
971
|
+
/// Norwegian `1ste` and `5te`. (`22de` needs no entry: `de` is dropped as a
|
|
972
|
+
/// filler word before the pieces are counted.)
|
|
973
|
+
fn is_ordinal_ending(token: &str) -> bool {
|
|
974
|
+
matches!(
|
|
975
|
+
token.to_lowercase().as_str(),
|
|
976
|
+
"st" | "nd" | "rd" | "th" | "er" | "ste" | "te"
|
|
977
|
+
)
|
|
978
|
+
}
|
|
979
|
+
|
|
947
980
|
fn parse_numeric(input: &str) -> Option<DatePoint> {
|
|
948
981
|
for (separator, month_first) in [('.', false), ('/', true), ('-', false)] {
|
|
949
982
|
let parts: Vec<&str> = input.split(separator).collect();
|
|
@@ -1538,6 +1571,100 @@ mod tests {
|
|
|
1538
1571
|
}
|
|
1539
1572
|
}
|
|
1540
1573
|
|
|
1574
|
+
#[test]
|
|
1575
|
+
fn a_word_in_front_of_a_date_never_turns_the_day_into_the_month() {
|
|
1576
|
+
// Each of these was once read with its day number as the month:
|
|
1577
|
+
// `chr 10 Nov 1788` as 10 October. A word the reader does not know
|
|
1578
|
+
// leaves the whole payload unparsed, so a writer keeps it as a phrase.
|
|
1579
|
+
for payload in [
|
|
1580
|
+
"chr 10 Nov 1788",
|
|
1581
|
+
"b 9 Jun 1750",
|
|
1582
|
+
"bapt. 3 May 1801",
|
|
1583
|
+
// Lower-case and full month names, and two-digit days.
|
|
1584
|
+
"chr 10 nov 1788",
|
|
1585
|
+
"chr 10 November 1788",
|
|
1586
|
+
"b 19 Jun 1750",
|
|
1587
|
+
"b. 19 june 1750",
|
|
1588
|
+
"bapt 3 MAY 1801",
|
|
1589
|
+
"Sunday 5 Jan 1882",
|
|
1590
|
+
// No month written as a word: nothing to anchor the order.
|
|
1591
|
+
"chr 10 11 1788",
|
|
1592
|
+
"chr Nov 1788",
|
|
1593
|
+
"b 1750",
|
|
1594
|
+
] {
|
|
1595
|
+
let date = parsed(payload);
|
|
1596
|
+
assert_eq!(date.kind, DateKind::Unparsed, "{payload:?}");
|
|
1597
|
+
assert_eq!(date.earliest, None, "{payload:?}");
|
|
1598
|
+
assert_eq!(date.phrase.as_deref(), Some(payload), "{payload:?}");
|
|
1599
|
+
assert_eq!(date.to_gedcom_5_5_1(), None, "{payload:?}");
|
|
1600
|
+
assert_eq!(date.to_gedcom_7(), None, "{payload:?}");
|
|
1601
|
+
}
|
|
1602
|
+
// The year is still there to sort by.
|
|
1603
|
+
assert_eq!(parsed("chr 10 Nov 1788").year_hint(), Some(1788));
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
#[test]
|
|
1607
|
+
fn a_known_qualifier_in_front_of_a_date_is_read_as_one() {
|
|
1608
|
+
let written = |text: &str| parsed(text).to_gedcom_5_5_1();
|
|
1609
|
+
let cases: &[(&str, &str)] = &[
|
|
1610
|
+
("by 5 October 1752", "BEF 5 OCT 1752"),
|
|
1611
|
+
("By 5 Oct 1752", "BEF 5 OCT 1752"),
|
|
1612
|
+
("by 1752", "BEF 1752"),
|
|
1613
|
+
("abt 10 Nov 1788", "ABT 10 NOV 1788"),
|
|
1614
|
+
("abt. 10 nov 1788", "ABT 10 NOV 1788"),
|
|
1615
|
+
("about 10 November 1788", "ABT 10 NOV 1788"),
|
|
1616
|
+
("c. 3 May 1801", "ABT 3 MAY 1801"),
|
|
1617
|
+
("ca. 3 May 1801", "ABT 3 MAY 1801"),
|
|
1618
|
+
("ca 3 may 1801", "ABT 3 MAY 1801"),
|
|
1619
|
+
("bef 19 Jun 1750", "BEF 19 JUN 1750"),
|
|
1620
|
+
("bef. 19 Jun 1750", "BEF 19 JUN 1750"),
|
|
1621
|
+
("before 19 June 1750", "BEF 19 JUN 1750"),
|
|
1622
|
+
("aft 9 Jun 1750", "AFT 9 JUN 1750"),
|
|
1623
|
+
("aft. 9 Jun 1750", "AFT 9 JUN 1750"),
|
|
1624
|
+
("after 9 June 1750", "AFT 9 JUN 1750"),
|
|
1625
|
+
];
|
|
1626
|
+
for (text, expected) in cases {
|
|
1627
|
+
assert_eq!(written(text).as_deref(), Some(*expected), "{text:?}");
|
|
1628
|
+
}
|
|
1629
|
+
// A word that merely begins like a qualifier is not one.
|
|
1630
|
+
assert_eq!(parsed("byre 5 Oct 1752").kind, DateKind::Unparsed);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
#[test]
|
|
1634
|
+
fn a_day_with_its_ordinal_ending_is_read_either_side_of_the_month() {
|
|
1635
|
+
type Case = (&'static str, i32, u8, u8);
|
|
1636
|
+
let cases: &[Case] = &[
|
|
1637
|
+
("Feb 11 th 1809", 1809, 2, 11),
|
|
1638
|
+
("Feb 11th 1809", 1809, 2, 11),
|
|
1639
|
+
("April 15th, 1873", 1873, 4, 15),
|
|
1640
|
+
("January 1st 1800", 1800, 1, 1),
|
|
1641
|
+
("11 th Feb 1809", 1809, 2, 11),
|
|
1642
|
+
("1st January 1800", 1800, 1, 1),
|
|
1643
|
+
("23rd nov 1788", 1788, 11, 23),
|
|
1644
|
+
("1er janvier 1800", 1800, 1, 1),
|
|
1645
|
+
("5te Januar 1800", 1800, 1, 5),
|
|
1646
|
+
("22de Juni 1848", 1848, 6, 22),
|
|
1647
|
+
];
|
|
1648
|
+
for (payload, year, month, day) in cases {
|
|
1649
|
+
let date = parsed(payload);
|
|
1650
|
+
assert_eq!(date.kind, DateKind::Exact, "{payload:?}");
|
|
1651
|
+
assert_eq!(
|
|
1652
|
+
date.earliest,
|
|
1653
|
+
Some(DatePoint {
|
|
1654
|
+
year: *year,
|
|
1655
|
+
month: Some(*month),
|
|
1656
|
+
day: Some(*day)
|
|
1657
|
+
}),
|
|
1658
|
+
"{payload:?}"
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
// A number where the month should be, or a word where the ending
|
|
1662
|
+
// should be, is not guessed around.
|
|
1663
|
+
for payload in ["11 15 th 1873", "Feb 11 foo 1809", "11 foo Feb 1809"] {
|
|
1664
|
+
assert_eq!(parsed(payload).kind, DateKind::Unparsed, "{payload:?}");
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1541
1668
|
#[test]
|
|
1542
1669
|
fn what_cannot_be_read_keeps_its_text_and_says_so() {
|
|
1543
1670
|
for payload in ["Infant", "DEAD", "Unknown", "1799 or 12 April 1811"] {
|