Search Siblings or Parents.fh_lua

--[[
@Title: Search Siblings or Parents
@Author: Jane Taubman
@Version: 1.1
@LastUpdated: 29 Nov 2020
@Description: Search for a pair Siblings or a Child and Parent by Christian names. Searches children of all recorded Parents. 
1.1 V7 compatibility
]]

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


function main()
    local sName1, sSurname,sName2,sRelation = prompt()
    if sName1 == nil then return end
    local tblResults = createResultTable()
    -- Define Columns
    tblResults.indi1 = {title='Individual 1',sort=1}
    tblResults.life1 = {title='Life Dates', width=60}
    tblResults.recordid1 = {title='Id', width=60}
    tblResults.indi2 = {title='Individual 2'}
    tblResults.life2 = {title='Life Dates',width=60}
    tblResults.recordid2 = {title='Id', width=60}
    tblResults.father1 = {title='Father 1'}
    tblResults.father2 = {title='Father 2'}

    local function addRow(pi,ps) -- Defining output here inherits upvalues for the output table
        -- Add Row
        tblResults:newRow()
        -- Set Columns
        tblResults.indi1:set(pi:Clone())
        tblResults.life1:set(fhCallBuiltInFunction('lifedates',pi))
        tblResults.recordid1 :set(fhGetRecordId(pi))
        tblResults.indi2:set(ps:Clone())
        tblResults.life2:set(fhCallBuiltInFunction('lifedates',ps))
        tblResults.recordid2:set(fhGetRecordId(ps))
        tblResults.father1:set(fhGetItemPtr(pi,'~.~FATH>'))
        tblResults.father2:set(fhGetItemPtr(ps,'~.~FATH>'))
    end

    for pi in records('INDI') do
        local spiname = string.lower(allGivenNames(pi))
        local spisurname = string.lower(allSurnames(pi))
        if spiname:match(sName1) and spisurname:match(sSurname) then
            if sRelation == 0 then -- Sibling Search
                local sibList = allSiblings(pi)
                for _,ps in pairs(sibList) do
                    local spsname = string.lower(allGivenNames(ps))
                    if spsname:match(sName2) then
                        addRow(pi,ps)
                    end
                end
            else -- parent matching
                local parList = allParents(pi)
                for _,ps in pairs(parList) do
                    local spsname = string.lower(allGivenNames(ps))
                    if spsname:match(sName2) then
                        addRow(pi,ps)
                    end
                end
            end
        end
    end
    sTitle = "Siblings Matching "..sName1:titleCase()..' '..sSurname:titleCase()..' and '..sName2:titleCase()
    if sRelation == 1 then 
    sTitle = "Child Matching "..sName1:titleCase()..' '..sSurname:titleCase()..' with Parent matching '..sName2:titleCase()
	 end
    fhOutputResultSetTitles(sTitle, sTitle, "Printed Date: %#x")
    tblResults:outputResults()
end
---------------------------------------------------- Prompt

function prompt()
    local sPrompt =
[[First Person (leave Surname blank for all) %t
Given Name Contains %s
Surname Contains %s
Second Person %t
Given Name Contains %s
Relationship %l|Sibling|Parent|
    ]]
    local sName1 = ''
    local sSurname1 = ''
    local sName2 = ''
    local sRelation = 0
    local ret, sName1, sSurname1, sName2,sRelation =
    iup.GetParam("Search Siblings or Parents",nil,
    sPrompt,
    sName1, sSurname1,sName2,sRelation)
    if (ret == 0) or ret == false then
        return
    else
        return sName1:trim():lower(),sSurname1:trim():lower(),sName2:trim():lower(),sRelation
    end
end
---------------------------------------------------- Relations

function allSiblings(pi)
    local tParents = allParents(pi)
    local tSiblings = allChildren(tParents)
    tSiblings[fhGetRecordId(pi)] = nil -- Exclude Original Person
    return tSiblings
end

function allParents(pi)
    local tParents = {}
    local ps = fhNewItemPtr()
    local pf = fhNewItemPtr()
    local pf = fhNewItemPtr()
    local pp = fhNewItemPtr()
    ps:MoveTo(pi,'~.FAMC')
    while ps:IsNotNull() do
        pf = fhGetValueAsLink(ps)
        pp:MoveTo(pf,'~.HUSB>')
        if pp:IsNotNull() then
            table.insert(tParents,pp:Clone())
        end
        pp:MoveTo(pf,'~.WIFE>')
        if pp:IsNotNull() then
            table.insert(tParents,pp:Clone())
        end
        ps:MoveNext('SAME_TAG')
    end
    return tParents
end

function allChildren(tParents)
    local tChildren = {}
    local pc = fhNewItemPtr()
    for _,pi in ipairs(tParents) do
        i = 1
        pc:MoveTo(pi,'~.~CHIL>') -- INDI.~CHIL[1]>
        while pc:IsNotNull() do
            if pc:IsNotNull() then
                irec = fhGetRecordId(pc)
                tChildren[irec] = pc:Clone()
            end
            i = i + 1
            pc:MoveTo(pi,'~.~CHIL['..i..']>')
        end
    end
    return tChildren
end

function allGivenNames(pi)
    local tblall = {}
    local function addvalue(p,tag)
        n = fhGetItemText(p,tag)
        if n and n ~= '' then
            table.insert(tblall,n)
        end
    end
    local pn = fhNewItemPtr()
    pn:MoveTo(pi,'~.NAME')
    while pn:IsNotNull() do
        addvalue(pn,'~:GIVEN_ALL')
        addvalue(pn,'~._USED')
        addvalue(pn,'~.GIVN')
        addvalue(pn,'~.NICK')
        pn:MoveNext('SAME_TAG')
    end
    return table.concat(tblall,' ')
end

function allSurnames(pi)
    local tblall = {}
    local function addvalue(p,tag)
        n = fhGetItemText(p,tag)
        if n and n ~= '' then
            table.insert(tblall,n)
        end
    end
    local pn = fhNewItemPtr()
    local pf = fhNewItemPtr()
    local ph = fhNewItemPtr()
    pn:MoveTo(pi,'~.NAME')
    while pn:IsNotNull() do
        addvalue(pi,'~.NAME:SURNAME')
        addvalue(pi,'~.NAME.SURNAME')
        pn:MoveNext('SAME_TAG')
    end
    -- Married Women get Married Surnames
    if fhGetItemText(pi,'~.SEX') == 'Female' then
        pf:MoveTo(pi,'~.FAMS')
        while pf:IsNotNull() do
            ph = fhGetValueAsLink(pf)
            ph:MoveTo(ph,'~.HUSB>')
            if ph:IsNotNull() then
                pn:MoveTo(ph,'~.NAME')
                while pn:IsNotNull() do
                    addvalue(pn,'~:SURNAME')
                    addvalue(pn,'~.SURNAME')
                    pn:MoveNext('SAME_TAG')
                end
            end
            pf:MoveNext('SAME_TAG')
        end
    end
    return table.concat(tblall,' ')
end
---------------------------------------------------- Standard Functions

function string.titleCase(str)
    local function titleCasePart( first, rest )
        return first:upper()..rest:lower()
    end
    return string.gsub(str, "(%a)([%w_']*)", titleCasePart)
end

function records(type)
    local pi = fhNewItemPtr()
    local p2 = fhNewItemPtr()
    pi:MoveToFirstRecord(type)
    return function ()
        p2:MoveTo(pi)
        pi:MoveNext()
        if p2:IsNotNull() then return p2 end
    end
end
function string.trim(s)
   return (s:gsub("^%s*(.-)%s*$", "%1"))
end
function createResultTable()
    local tblOutput_mt = {} -- create metatable
    local iC = 0 -- Define count of lines
    local tblOutput = {} -- Define Columns Table
    tblOutput_mt.col = 0
    tblOutput_mt.seq = {}
    tblOutput_mt.__newindex =

    function (t,k,v)
        -- Set Values to Defaults if not supplied
        if v.content == nil then v.content = {} end
        if v.title == nil then v.title = k end
        if v.type == nil then v.type = 'notset' end
        if v.width == nil then v.width = 140 end
        if v.align == nil then v.align = 'align_left' end
        if v.sort == nil then v.sort = 0 end
        if v.sortAscending == nil then v.sortAscending = true end
        if v.sortType == nil then v.sortType = 'default' end
        if v.visibility == nil then v. visibility = 'show' end
        v.set =

        function(self,value)
            self.content[iC] = value
            if self.type == 'notset' then
                if type(value) == 'string' then
                    self.type = 'text'
                elseif type(value) == 'number' then
                    self.type = 'integer'
                    self.width = '30'
                else
                    self.type = 'item'
                end
            end
        end
        rawset(t,k,v) -- update original table
        local m = getmetatable(t)
        m.col = m.col + 1
        table.insert(m.seq,k)
    end
    tblOutput_mt.__call =

    function (t)
        local i = 0
        local m = getmetatable(t)
        local n = table.getn(m.seq)
        return function ()
            i = i + 1
            if i <= n then
                return t[m.seq[i]]
            end
        end
    end
    tblOutput.newRow = function(t)
        iC = iC + 1
    end
    tblOutput.rowCount = function(t)
        return iC
    end
    tblOutput.outputResults = function(self)
        if iC > 0 then
            for l in self() do
                fhOutputResultSetColumn(l.title, l.type, l.content, iC, l.width,l.align,l.sort,l.sortAscending,l.sortType,l.visibility )
            end
        else
            fhMessageBox('Sorry, no results found')
        end
    end
    setmetatable(tblOutput, tblOutput_mt)
    return tblOutput
end

if not(table.getn) then
  function table.getn(t)
      local count = 0
      for _, __ in pairs(t) do
          count = count + 1
      end
      return count
   end
end



main()

Source:Search-Siblings-or-Parents-1.fh_lua