gwt-import-excel¶
Turns rows of a research workbook (.xlsx), such as a cousin's spreadsheet or an archive's index, into a gwt-apply change list for review. It never writes to Gramps and never decides who is a duplicate: gwt-apply's preflight does. Python 3.8+, standard library only.
cd importxl
gwt-apply --save-snapshot tree.json.gz # or a gwt-check dump
python3 gwt_import_excel.py cousin.xlsx --mapping map.json --batch cousin-ana \
--tree tree.json.gz --out changes.jsonl --report report.json
gwt-apply changes.jsonl --snapshot tree.json.gz # dry run first, as always
--batch names the workbook. It goes into every key, so two workbooks never share a key, and it must stay the same when you rerun the same workbook. Use a new batch for every other workbook.
A rerun under the same batch does not correct what an earlier apply wrote. gwt-apply skips every key it already made, so a date or a place fixed in the workbook after the apply stays as it was in the tree. Put a correction in the tree with an update_event or update_person op, or by hand. --out and --report are never overwritten without --force.
Mapping file¶
Workbooks differ, so nothing is hard-coded. The mapping names the sheet, the header row and which column holds what:
{"ruling": "R-1",
"sheets": [{"sheet": "People", "header_row": 1,
"columns": {"first_name": "Given", "surname": "Surname", "gender": "Sex",
"birth_date": "Born", "birth_place": "Birth place", "birth_place_id": "Birth place ID",
"death_date": "Died", "death_place": "Death place", "death_place_id": "Death place ID",
"baptism_date": "Baptised", "burial_date": "Buried",
"source": "Source", "page": "Page", "namesake_reason": "Why another person"}}]}
Births, baptisms, christenings, deaths and burials each take <event>_date, <event>_place and <event>_place_id columns (baptism_date, christening_place, burial_place_id...), and each becomes an event of that type. Only first_name and surname are needed for a row to import. A field name the tool does not know (a typo such as bautismo_date), a mapped column that is not in the header, or a header name that appears twice, stops the run and names the column. ruling goes on every op, as gwt-apply requires.
What it does with each row¶
- Keys. Every op of a row gets the key
xl-<batch>-<sheet>-<hash>-r<row>-<name>-<hash>. The hashes cover the exact sheet name and the whole name, so two sheets whose names start alike never share a key. The name is part of it, so a row that moves after a rerun gets a new key, and preflight then reports it as a namesake of the person the old row made. It is never silently merged into someone else. - Namesakes. With
--tree, the report lists undernamesakesevery row whose name matches a person in the tree, either the full name or the first given name and first surname as preflight compares them, and every row that matches an earlier row of the same import. The importer keeps them all in the change list. A row with nothing in thenamesake_reasoncolumn goes in as a plainadd_person, and gwt-apply refuses the whole list until someone decides: delete the row, or write in that column why it is another person and run again. A row with a reason getsallow_namesake, that reason, andnamesake_ofnaming every match. A reason on a row with no match is listed undernamesake_reason_unused. - People who may be alive. A row with nothing in the death columns is held out of the change list and listed under
living_heldwhen its birth is less than 100 years ago or it has no birth date at all. As in the rest of the toolkit, a baptism or christening date stands in for a missing birth date, and a burial counts as a death, so a historic baptism or burial index is not held back.--allow-livingimports them. - Places. The importer never creates a place, because a place in the tree has a type and a hierarchy that a spreadsheet does not give. A
*_place_idcolumn holding a Gramps ID (P0042) is used as is. Otherwise the place text is matched by name, ignoring case and accents, against the tree's places. One match is used. Several matches go underplaces_ambiguousand none go underplaces_unmatched. In both cases the event keepsPlace as written: ...in its description. An ID that is not a Gramps ID, or not in the tree, goes underplace_ids_unknown. - Dates. Checked against the real calendar, leap years included:
31/02/1901,2026-02-29or99 March 1901are refused. An explicit zero (1901-02-00,00 March 1901,0000) is refused too, since only an absent part means unknown. It reads1901,1901-03,1901-03-14,14/03/1901,14-03-1901and14.03.1901(day first), month names in English, Spanish and French, whole words or the usual abbreviations (Mar 1901,14 de marzo de 1901,1er janvier 1901,15 août 1901), and the prefixesc.,ca.,circa,abt,hacia,vers,bef,antes de,avant,aft,después de,après. A day-first date that would also read month first (03/04/1901) is listed underday_first_assumed. Anything else is not stored as a date. The event keepsDate as written: ...in its description, and the row goes underdate_problems. Missing parts are never filled in. A cell holding only?makes no event. - Excel dates and formulas. A numeric cell with a date style (built-in or custom, such as
d/m/yyyy) is converted from its serial number to an ISO date (1900 and 1904 systems; serial 0 is valid only in the 1904 system, and the 1900 leap-year bug day is refused). A number whose format shows only a time of day or elapsed time (built-ins 18-21, 32, 33, 45-47, or a custom code withh/s/[h], such as[h]:mm:ss) is not a date. It is kept as typed text and listed undertime_cells. A plain number is kept as typed, so1901is the year 1901, and a serial is never read as a year. A formula cell gives its cached value, which the tool does not recalculate. Every such cell is listed underformula_cells, also when it is a date or an error. An error cell (#REF!) is empty and listed undererror_cells. - Gender.
M,H,V,varón,hombre,masculino,hommeandmasculinare male;F,mujer,hembra,femenino,femmeandfémininare female. An empty cell or?is unknown, and anything else is unknown and listed undergender_unread. - Sources. The source column becomes one source per distinct title and one citation per row, attached to the person and the events. gwt-apply reuses a source that already has that title. A row with no page gets the page
(no page in workbook, row <sheet>!<row>), so two rows never share a page-less citation. A row with no source gets a[no source]research note instead and is listed underno_source. - Skipped rows. Rows with an incomplete name are listed with the reason.
Limits¶
People, their births, baptisms, christenings, deaths and burials, and their citations only. No families, no parents: add them by hand or in a second pass with the reviewed rows. A workbook part that unpacks to more than 100 MB, a workbook that unpacks to more than 200 MB, a row past 1,048,576, a column past 16,384 or a shared-string index out of range stops the run. Only cells with text are kept in memory, so a stray cell far to the right costs nothing. A broken workbook ends with a message and exit 2, never a traceback.