Surname Summary Report.fh_lua

--[[
@Title: Surname Summary Report
@Author: Jane Taubman
@LastUpdated: 30 Nov 2020
@Version: 1.4
@Description: Counts and Lists All Surnames in the File
1.1 Added earliest and latest dates for each surname and with report titles.
1.2 Add Soundex column
1.3 Added the use of createResultSet() function.
1.4 V7 Compatibility
]]

function main()
    local tblSurnames = {} -- Define array for Surnames
    local tblMin = {}
    local tblMax = {}
    local tblMale = {}
    local tblFemale = {}
    local tblOther = {}
    for pi in records('INDI') do
        -- For each Person Add the Surname to the list
        local strSurname = fhGetItemText(pi,'INDI.NAME:Surname')
        tblSurnames[strSurname] = inc(tblSurnames[strSurname])
		 local s = fhGetItemText(pi,'~.SEX')
		 if s == 'Male' then 
				tblMale[strSurname] = inc(tblMale[strSurname])
			else
				if s == 'Female' then 
					tblFemale[strSurname] = inc(tblFemale[strSurname])
			else 
				tblOther[strSurname] = inc(tblOther[strSurname])
			  end
		  end
        local min,max = daterange(pi)
        if tblMin[strSurname] and min then
            if min < tblMin[strSurname] then tblMin[strSurname] = min end
        else
            if min then tblMin[strSurname] = min end
        end
        if tblMax[strSurname] and max then
            if max > tblMax[strSurname] then tblMax[strSurname] = max end
        else
            if max then tblMax[strSurname] = max end
        end        
    end
    -- Result set columns for Name and Qty
    local tblResults = createResultTable()
    tblResults.surname = {title='Surname',sort='2'}
    tblResults.count = {title='Count', align="align_right",sort='1',sortAscending=false}
    tblResults.min = {title='Earliest', align="align_right"}
    tblResults.max = {title='Latest', align="align_right"}
    tblResults.male = {title='Male', align="align_right"}
    tblResults.female = {title='Female', align="align_right"}
    tblResults.other = {title='Other', align="align_right"}
    tblResults.soundex = {title='Soundex', width=40}
    soundex = NewSoundex()
    for strSurname, iQty in pairs(tblSurnames) do
        tblResults:newRow()
        tblResults.count:set(iQty)
        tblResults.surname:set(strSurname)
        tblResults.min:set(tblMin[strSurname] or 0)
        tblResults.max:set(tblMax[strSurname] or 0)
        tblResults.male:set(tblMale[strSurname] or 0)
        tblResults.female:set(tblFemale[strSurname] or 0)
        tblResults.other:set(tblOther[strSurname] or 0)
        tblResults.soundex:set(' '..soundex(strSurname
))
    end
    local project = fhGetContextInfo('CI_PROJECT_NAME')
    fhOutputResultSetTitles("Surname Summary Report","Surname Summary Report for "..project, "Printed Date: %#x")
    tblResults:outputResults()
end
---------------------------------- Additional Functions

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 inc(i,s)
    return (i or 0) + (s or 1)
end

function daterange(ptr)
       local function compdate(year,minyear,maxyear)
         iYear = tonumber(year)
         if iYear then
           if iYear > (maxyear or 0) then
               maxyear = iYear
           end
           if iYear < (minyear or 99999) then
               minyear = iYear
           end
         end
         return minyear,maxyear
       end
    local eventPtr = fhNewItemPtr()
    local famPtr = fhNewItemPtr()
    local famsPtr = fhNewItemPtr()
    local minyear
    local maxyear
    eventPtr:MoveToFirstChildItem(ptr)
    while eventPtr:IsNotNull() do
        local year = fhGetItemText(eventPtr,'~.DATE:YEAR')
        minyear,maxyear = compdate(year,minyear,maxyear)
        eventPtr:MoveNext()
    end
    -- Family As Spouse Events
    famsPtr:MoveTo(ptr,'~.FAMS')
    while famsPtr:IsNotNull() do
        famPtr = fhGetValueAsLink(famsPtr)
        eventPtr:MoveToFirstChildItem(famPtr)
        while eventPtr:IsNotNull() do
            local year = fhGetItemText(eventPtr,'~.DATE:YEAR')
            minyear,maxyear = compdate(year,minyear,maxyear)
            eventPtr:MoveNext()
        end
        famsPtr:MoveNext()
    end
    return minyear,maxyear
end

function NewSoundex(tblSoundex) -- Prototype Soundex Calculator
    
    local tblSoundex = tblSoundex or { } -- Soundex dictionary cache of previously coded Names
    tblSoundex[""] = "Z000" -- Seed with null string special case
    
    local tblCodeNum = { -- Soundex code number table
        A=0,E=0,I=0,O=0,U=0,Y=0, -- H=0,W=0, -- H & W are ignored
        B=1,F=1,P=1,V=1,
        C=2,G=2,J=2,K=2,Q=2,S=2,X=2,Z=2,
        D=3,T=3,
        L=4,
        M=5,N=5,
        R=6
    }
    local strSoundex
    local tblSoundex = tblSoundex or {}
    return function (strAnyName)
        strAnyName = string.upper(strAnyName:gsub("[^%a]","")) -- Make name upper case letters only
        strSoundex = tblSoundex[strAnyName] -- If already coded in cache then return previous Soundex code
        if strSoundex then return strSoundex end
        strSoundex = string.sub(strAnyName,1,1) -- Soundex starts with initial letter
        local strLastNum = tblCodeNum[strSoundex] -- Set initial Soundex code number
        for i = 2, string.len(strAnyName) do
            local strCodeNum = tblCodeNum[string.sub(strAnyName,i,i)] -- Step through Soundex code of each subsequent letter
            if strCodeNum then
                if strCodeNum > 0 and strCodeNum ~= strLastNum then -- Not a vowel nor same as Soundex preceeding code
                    strSoundex = strSoundex..strCodeNum -- So append Soundex code until 4 chars long
                    if string.len(strSoundex) == 4 then
                        tblSoundex[strAnyName] = strSoundex -- Save code in cache for future quick lookup
                        return strSoundex
                    end
                end
                strLastNum = strCodeNum -- Save as Soundex preceeding code, unless H or W
            end
        end
        strSoundex = string.sub(strSoundex.."0000",1,4) -- Pad code with zeroes to 4 chars long
        tblSoundex[strAnyName] = strSoundex -- Save code in cache for future quick lookup
        return strSoundex
    end -- anonymous function
end -- function Soundex

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.prototype = {
               title = ' ',
               type='notset',
               width=140,
               align='align_left',
               sort=0, 
               sortAcending=true, 
               sortType='default',
               visibility='show'
             }
    tblOutput_mt.prototype.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

    tblOutput_mt.metavalues = {__index = tblOutput_mt.prototype}

    tblOutput_mt.__newindex =
       function (t,k,v)
         -- Set Values to Defaults via 2nd Metatable
         setmetatable(v,tblOutput_mt.metavalues)
         if v.content == nil then v.content = {} 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)
          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
       end
    setmetatable(tblOutput, tblOutput_mt)
    return tblOutput
end

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

function unpack(t)
    return table.unpack(t)
end
end

---------------------------------- Call Main
main()

Source:Surname-Summary-Report.fh_lua