Search All Possible Names.fh_lua

--[[
@title: Search All Possible Names
@author: Jane Taubman
@lastupdated: November 2020
@version: 1.6
@Licence: This plugin is copyright (c) 2020 Jane Taubman and contributors, and is licensed under the MIT License
which is hereby incorporated by reference (see https://pluginstore.family-historian.co.uk/fh-plugin-licence)
@description:
Script reads through all individuals and lists all variations of Names from the Name field
and for married women lists them under their married names as well, where the
given name and surname fields contain the entered search fields.
Please Note: All family as Spouse occurrences will be searched even if there is no marriage recorded.
@changes:
1.2 If user cancels the prompt it now simply returns
1.2 Corrected incorrect operation where user presses enter rather than pressing the OK button.
1.3 Added option to search using basic soundex for both the name and surname.
1.4 Added Default values from the currently selected record if there is one
1.5 Fix identification of Secondary Names and a search of subfields for Nickname, Used Name and the Surname and Given Name subfields.
1.6 V7 Compatibility
]]

require("iuplua");
iup.SetGlobal("CUSTOMQUITMESSAGE","YES");

function trim(s)
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end
-- Convert String to Soundex

function soundex(str)
    local codes = {
        A=0,E=0,I=0,O=0,U=0,Y=0,H=0,
        B=1,P=1,F=1,V=1,
        C=2,S=2,G=2,J=2,K=2,Q=2,X=2,Z=2,
        D=3,T=3,
        L=4,
        M=5,N=5,
        R=6
    }
    local strSoundex = ''
    local strCap = string.upper(str)
    local strSoundex = string.sub(strCap,1,1)
    local strRest = string.sub(strCap,2)
    for letter in string.gmatch(strRest, "%a") do
        local l = string.len(strSoundex)
        local strLast = string.sub(strSoundex,l,l)
        if codes[letter] ~= nil then
            if codes[letter] ~= 0 and codes[letter] ~= tonumber(strLast) then
                strSoundex = strSoundex..codes[letter]
            end
        end
    end
    strSoundex = strSoundex..'0000'
    return string.sub(strSoundex,1,4)
end
-- Convert all Words To Soundex

function soundexall(str)
    local strSoundex = ''
    for word in string.gmatch(str, "%a+") do
        strSoundex = strSoundex..' '..soundex(word)
    end
    return strSoundex
end

function comparestring(string1,string2,bUseSoundex)
    local str1 = trim(string1:lower())
    local str2 = trim(string2:lower())
    if bUseSoundex then
        str1 = soundexall(str1)
        str2 = soundexall(str2)
    end
    local ipos
    if string2 == nil then
        return true
    else
        ipos = string.find(str1,str2)
        return not(ipos == nil)
    end
end

function chkRow(sSurname,sGiven,ptrIndi,ptrFam,type)
    if comparestring(sSurname,strSurname,bUseSoundex) and comparestring(sGiven,strFirstName,bUseSoundex) then
        count = count + 1
        tblSurname[count] = sSurname:upper()
        tblGivenAll[count] = sGiven
        tblSoundex[count] = soundexall(tblSurname[count])..' '..soundexall(tblGivenAll[count])
        tblRecord[count] = ptrIndividual:Clone()
        tblRecordNo[count] = fhGetRecordId(ptrIndividual)
        if type == 3 then
            tblFamilyRecord[count] = ptrFam:Clone()
            tblMarriageDate[count] = fhGetItemPtr(ptrFam,'FAM.MARR.DATE')
        end
        tblLifeDates[count] = fhCallBuiltInFunction('Lifedates',ptrIndividual)
        
        tblNameType[count] = tblTypes[type]
    end
end
-- Load IUP for Prompt Window.
require( "iuplua" )
-- If there is a current individual record grab the details as default for the search
ptrList = fhGetCurrentRecordSel('INDI')
if #ptrList > 0 then
    surname = string.lower(fhGetItemText(ptrList[1],'~.NAME:SURNAME'))
    forenames = string.lower(fhGetItemText(ptrList[1],'~.NAME:GIVEN_ALL'))
else
    surname = ''
    forenames = ''
end
-- Prompt User to Confirm Options
bOK,strFirstName,strSurname,bSoundex =
iup.GetParam("Search All Possible Names", param_action,
"Given Name Contains: %s\n"..
"Surname Contains: %s\n"..
"Use Soundex for search: %b\n"
,
forenames,surname,0)
if not(bOK) then
    return
end
if bSoundex == 1 then
    bUseSoundex = true
else
    bUseSoundex = false
end
ptrIndividual = fhNewItemPtr() -- declare pointer
ptrIndividual:MoveToFirstRecord('INDI') -- set the first to point to the first Note record
ptrName = fhNewItemPtr()
ptrFamS = fhNewItemPtr()
ptrFam = fhNewItemPtr()
count = 0
-- Define Columns
tblSurname = {}
tblGivenAll = {}
tblSoundex = {}
tblRecord = {}
tblRecordNo = {}
tblFamilyRecord = {}
tblLifeDates = {}
tblMarriageDate = {}
tblNameType = {}
tblTypes = {'Primary','Secondary','Married','Subfield'}
while not ptrIndividual:IsNull() do
    -- Get All Names from Name Fields
    ptrName:MoveTo(ptrIndividual,"~.NAME") -- Get the Name for the INDI Record.
    local index = 1
    local type = 1
    while not ptrName:IsNull() do
        chkRow(fhGetItemText(ptrName,'~:SURNAME'),fhGetItemText(ptrName,'~:GIVEN_ALL'),ptrIndividual,nil,type)
        -- For Each Name Look for subfields
        local  sSurname = fhGetItemText(ptrName,'~:SURNAME')
        local  sGivenAll = fhGetItemText(ptrName,'~:GIVEN_ALL')
        -- INDI.NAME[1].GIVN        
        local sGiven = fhGetItemText(ptrName,'~.GIVN')
        if sGiven ~= "" and sGiven ~= sGivenAll then
            chkRow(sSurname,sGiven,ptrIndividual,nil,4)
        end
        -- INDI.NAME[1].NICK
        local sGiven = fhGetItemText(ptrName,'~.NICK')
        if sGiven ~= "" and sGiven ~= sGivenAll then
            chkRow(sSurname,sGiven,ptrIndividual,nil,4)
        end
        -- INDI.NAME[1]._USED
        local sGiven = fhGetItemText(ptrName,'~._USED')
        if sGiven ~= "" and sGiven ~= sGivenAll then
            chkRow(sSurname,sGiven,ptrIndividual,nil,4)
        end
        -- INDI.NAME[1].SURN
        local sSurname = fhGetItemText(ptrName,'~.SURN')
        if sSurname ~= '' and sSurname ~= fhGetItemText(ptrName,'~:SURNAME') then
            chkRow(sSurname,sGivenAll,ptrIndividual,nil,4)
        end

        -- For Females check each set of Forenames combined with the Husbands surname.
        strSex = fhGetItemText(ptrIndividual,'INDI.SEX')
        if (strSex == 'Female') then
            ptrFamS:MoveTo(ptrIndividual,"~.FAMS")
            while not ptrFamS:IsNull() do
                ptrFam = fhGetValueAsLink(ptrFamS)
                famStat = fhGetItemText(ptrFam,'~._STAT')
                if famStat ~= 'Never Married' and famStat ~= 'Unmarried Couple' then
                    local sSurname = fhGetItemText(ptrFam,'~.HUSB>NAME:SURNAME')
                    local sGiven   = fhGetItemText(ptrName,'~:GIVEN_ALL')
                    local sGivenAll = sGiven
                    chkRow(sSurname,sGiven,ptrIndividual,ptrFam,3)
                    -- INDI.NAME[1].GIVN
                    local sGiven = fhGetItemText(ptrName,'~.GIVN')
                    if sGiven ~= "" and sGiven ~= sGivenAll then
                        chkRow(sSurname,sGiven,ptrIndividual,nil,4)
                    end
                    -- INDI.NAME[1].NICK
                    local sGiven = fhGetItemText(ptrName,'~.NICK')
                    if sGiven ~= "" and sGiven ~= sGivenAll then
                        chkRow(sSurname,sGiven,ptrIndividual,nil,4)
                    end
                    -- INDI.NAME[1]._USED
                    local sGiven = fhGetItemText(ptrName,'~._USED')
                    if sGiven ~= "" and sGiven ~= sGivenAll then
                        chkRow(sSurname,sGiven,ptrIndividual,nil,4)
                    end
                end
                ptrFamS:MoveNext("SAME_TAG") -- next Family as spouse
            end
        end
        index = index + 1
        type = 2
        ptrName:MoveNext("SAME_TAG") -- next Name
    end
    ptrIndividual:MoveNext() -- next individual record
end
if (#tblSurname > 0) then
    strTitle = "Matches for "..strFirstName..' '..strSurname
    strPrintTitle = "Search All Possible Names"
    fhOutputResultSetTitles(strTitle, strPrintTitle, strTitle.." (%#x)")
    fhOutputResultSetColumn("Surname", "text", tblSurname, #tblSurname, 80, "align_left", 1, true)
    fhOutputResultSetColumn("Given Names", "text", tblGivenAll, #tblSurname, 80, "align_left", 2, true)
    fhOutputResultSetColumn("Type", "text", tblNameType, #tblSurname, 40, "align_left", 0, true)
    fhOutputResultSetColumn("Lifedates", "text", tblLifeDates, #tblSurname, 40, "align_left", 3, true)
    fhOutputResultSetColumn("Marriage", "item", tblMarriageDate, #tblSurname, 80, "align_left", 0, true)
    fhOutputResultSetColumn("Record", "item", tblRecord, #tblSurname, 140, "align_left")
    fhOutputResultSetColumn("Ref", "integer", tblRecordNo, #tblSurname, 20, "align_right")
    fhOutputResultSetColumn("Family", "item", tblFamilyRecord, #tblSurname, 140, "align_left")
    fhOutputResultSetColumn("Soundex", "text", tblSoundex, #tblSurname, 140, "align_left")
    
else
    fhMessageBox('No matches found for '..strFirstName..' '..strSurname)
end

Source:Search-All-Possible-Names.fh_lua