--[[ @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