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