On the FHU mailing list Kath asked
is there yet any way to export a GEDCOM file contain only Name, Birth and Death?
I want to include Birth and Death places without any qualification like
Address, Age, Cause of Death, also Name without Known As etc. ONLY basic basic basic.
[FHU] GEDCOM basic export 28th Nov 2018
The answer is of course yes, you need to just to write a plugin, or use the “Clean Unwanted Fields” plugin already available in the plugin store. As a test I spent an hour writing a simple plugin to perform the task and thought it would be a good idea to do a walk through of how the plugin works, in case anyone needs to write something similar.
Rather than writing a plugin to export a cut down file, it’s much easier to delete everything you don’t need from a copy of the project. So the plugin needs to delete all events other than those required. Delete all unneeded record types. Delete all subfields from the NAME and remove flags.
When planning any program I try to use functions when ever possible to prevent repeating code. With this plugin it’s not completely optimised, but I have used both functions from the Code Snippets on FHUG as well as a couple functions written direct for this plugin.
The first thing to do is to set up a basic plugin using the Insert Header option on the plugin editor and then adding a main function and a call to it.
1 2 3 4 5 6 7 8 9 10 11 12 13 | --[[ @Title: BMD Only Gedcom @Author: Jane Taubman @Version: 0.1 @LastUpdated: Nov 2018 @Description: Deletes all Facts other than Birth, Marriage and Death, and all Sources, Media, Repositories and Submitters ]] function main() end --------------------------------------------------- Additional Functions --------------------------------------------------- Call Main main() |
The Main Function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | function main() local sMsg = "Warning:\n This plugin is designed to delete all data other than BMD Dates and Places from the current file.\n" if fhGetContextInfo('CI_APP_MODE') == 'Project Mode' then sMsg = sMsg.."/nYou have a Project open. \n\nPlease confirm you want to delete the data on" ..fhGetContextInfo('CI_PROJECT_NAME') else sMsg = sMsg.."\n\nPlease confirm you want to delete the data on " ..fhGetContextInfo('CI_GEDCOM_FILE') end ret = fhMessageBox(sMsg,'MB_OKCANCEL','MB_ICONEXCLAMATION') if ret ~= 'OK' then return end for pi in records('INDI') do clnFacts(pi,{'BIRT','DEAT'}) end for pf in records('FAM') do clnFacts(pf,{'MARR'}) end -- Delete all unused Place records dltRecords('_PLAC',false) -- Delete All Unneeded record types dltRecords('NOTE',true) dltRecords('SOUR',true) dltRecords('OBJE',true) dltRecords('REPO',true) dltRecords('SUBM',true) fhMessageBox('Process Complete','MB_OK','MB_ICONINFORMATION') end |
The main function actions the deletes which Kath requested, after prompting the user to confirm they want to delete the data.
- Lines 1-14 Prompt the User
- Lines 15-17 Loops through all the Individual Records deleting all Events other than Birth and Death
- Lines 18-20 Loop through the Family Records deleteing everything other than Marriage.
- Line 22 Removes all unused Place Records using dltRecords
- Lines 25-28 Remove all Note, Source, Media, Repository and Submitter Records using dltRecords.
I prefer to use a function for the core logic and add a simple
1 | main() |
to the end of the source. The reasons for this are:
- Functions used must have already been defined in the source
- Using a function for main means you can put the core logic at the top of the source code.
There are quite a few standard calls in the main function, such as
- fhMessageBox
- fhContextInfo
Built in Family Historian functions normally start with fh and are documented in the plugin help.
There are also several functions, which are included in the source of the plugin which I will explain below, the linked functions come from the Code snippets section of the Knowledge base.
Functions
- clnFacts(p,tblKeep)
- dltItem(p,sTag)
- stripFact(pFact,tblKeep)
- stripItem(pi,sTag)
- dltRecords(sType,bAll)
- inList(tblList,value)
- facts(pi)
- records(type)
clnFacts
clnFacts takes two parameters, a pointer to a record and a table containing the Facts to keep. When called from main, the table contains either BIRT and DEAT (16) or MARR (19).
It takes a two pass approach to deleting the extra facts. It first creates a local table to contain all the items to be deleted, then uses the table to remove them. Note the use of Clone when adding the pointer to the table, if you don’t do this, you will end up with a table all of the entries point to the last fact returned.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function clnFacts(p,tblKeep) local tblFacts = {} for px in facts(p) do table.insert(tblFacts,px:Clone()) end -- Delete un-needed facts for _,pFact in ipairs(tblFacts) do local sType = fhGetTag(pFact) if not(inList(tblKeep,sType)) then fhDeleteItem(pFact) else stripFact(pFact,{'DATE','PLAC'}) end end -- Clean up other items stripItem(p,'NAME',{}) dltItem(p,'_FLGS',{}) dltItem(p,'NOTE2',{}) end |
dltItem
The dltItem takes two parameters a Record level pointer and a tag to remove, it works in the same way as clnFacts building a list and then deleting all the items in the list.
1 2 3 4 5 6 7 8 9 10 11 12 13 | function dltItem(p,sTag) local tblLine = {} local pc = fhNewItemPtr() -- declare pointer local sTag2 = "~."..sTag pc:MoveTo(p,sTag2) while pc:IsNotNull() do table.insert(tblLine,pc:Clone()) pc:MoveNext("SAME_TAG") end for _,pLine in ipairs(tblLine) do fhDeleteItem(pLine) end end |
stripFact
The stripFact function takes two parameters, the first is a pointer to a Fact and the second a list of child items to retain. When called from clnFact it will have DATE and PLAC as the child items to retain. Again it builds the list and then deletes the listed items.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function stripFact(pFact,tblKeep) local tblLine = {} local pc = fhNewItemPtr() -- declare pointer pc:MoveToFirstChildItem(pFact) while pc:IsNotNull() do local sType = fhGetTag(pc) if not(inList(tblKeep,sType)) then table.insert(tblLine,pc:Clone()) end pc:MoveNext("SAME_TAG") end for _,pLine in ipairs(tblLine) do fhDeleteItem(pLine) end end |
stripItem
stripItem takes two parameters a record pointer and a tag name, it’s similar to dltItem, but it only removes the child items of the item provided, so for example called with a Tag of NAME it will remove fields such as Given name used, but leave the NAME value intact.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function stripItem(pi,sTag) local tblLine = {} local pc = fhNewItemPtr() -- declare pointer local sTag2 = "~."..sTag pc:MoveTo(pi,sTag2) pc:MoveToFirstChildItem(pc) while pc:IsNotNull() do table.insert(tblLine,pc:Clone()) pc:MoveNext("SAME_TAG") end for _,pLine in ipairs(tblLine) do fhDeleteItem(pLine) end end |
dltRecords
dltRecords takes two parameters a record type value and a boolean (true, false) which if set to true deletes all records, where as false only deletes unused records.
It uses a slightly different method of looping through the list as it uses two pointers one to step through the list and one to delete the previous record, if you just deleted the pointer directly you would lose your place in the records list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function dltRecords(sType,bAll) local ptr = fhNewItemPtr() local dltptr = fhNewItemPtr() ptr:MoveToFirstRecord(sType) while ptr:IsNotNull() do lnks = fhCallBuiltInFunction('LinksTo',ptr) if lnks == 0 or bAll then dltptr:MoveTo(ptr) ptr:MoveNext() fhDeleteItem(dltptr) else ptr:MoveNext() end end end |
You can download the full source code below