Lookup Missing Census Facts.fh_lua--[[
@Title: Lookup Missing Census Facts
@Type: Standard
@Author: Mike Tate
@Contributors:
@Version: 5.3
@Keywords:
@LastUpdated: 09 Feb 2026
@Licence: This plugin is copyright (c) 2026 Mike Tate & 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: Checks selected Individuals for chosen Census Events and produces a web page to search online databases for missing Census Records.
Estimated BMD dates are used, where none are recorded. Womens married names are used where the Census is after the marriage date (or after the birth of the first child).
@Version Log: See end of file.
]]
if fhGetAppVersion() > 5 then fhSetStringEncoding("UTF-8") end
--[[
@Title: aa Library Functions Preamble
@Author: Mike Tate
@Version: 3.9
@LastUpdated: 29 Jan 2026
@Description: All the library functions prototype closures for Plugins.
]]
--[[
@Module: +fh+stringx_v3
@Author: Mike Tate
@Version: 3.0
@LastUpdated: 19 Sep 2020
@Description: Extended string functions to supplement LUA string library.
@V3.0: Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility; Added inert(strTxt) function;
@V2.5: Support FH V6 Encoding = UTF-8;
@V2.4: Tolerant of integer & nil parameters just link match & gsub;
@V1.0: Initial version.
]]
local function stringx_v3()
local fh = {} -- Local environment table
-- Supply current file encoding format --
function fh.encoding()
if fhGetAppVersion() > 5 then return fhGetStringEncoding() end
return "ANSI"
end -- function encoding
-- Split a string using "," or chosen separator --
function fh.split(strTxt,strSep)
local tblFields = {}
local strPattern = string.format("([^%s]+)", strSep or ",")
strTxt = tostring(strTxt or "")
strTxt:gsub(strPattern, function(strField) tblFields[#tblFields+1] = strField end)
return tblFields
end -- function split
-- Split a string into numbers using " " or "," or "x" separators -- Any non-number remains as a string
function fh.splitnumbers(strTxt)
local tblNum = {}
strTxt = tostring(strTxt or "")
strTxt:gsub("([^ ,x]+)", function(strNum) tblNum[#tblNum+1] = tonumber(strNum) or strNum end)
return tblNum
end -- function splitnumbers
local strMagic = "([%^%$%(%)%%%.%[%]%*%+%-%?])" -- UTF-8 replacement for "(%W)"
-- Hide magic pattern symbols ^ $ ( ) % . [ ] * + - ?
function fh.plain(strTxt)
-- Prefix every magic pattern character with a % escape character,
-- where %% is the % escape, and %1 is the original character capture.
strTxt = tostring(strTxt or ""):gsub(strMagic,"%%%1")
return strTxt
end -- function plain
-- matches is plain text version of string.match()
function fh.matches(strTxt,strFind,intInit)
strFind = tostring(strFind or ""):gsub(strMagic,"%%%1") -- Hide magic pattern symbols
return tostring(strTxt or ""):match(strFind,tonumber(intInit))
end -- function matches
-- replace is plain text version of string.gsub()
function fh.replace(strTxt,strOld,strNew,intNum)
strOld = tostring(strOld or ""):gsub(strMagic,"%%%1") -- Hide magic pattern symbols
return tostring(strTxt or ""):gsub(strOld,function() return strNew end,tonumber(intNum)) -- Hide % capture symbols
end -- function replace
-- Hide % escape/capture symbols in replacement so they are inert
function fh.inert(strTxt)
strTxt = tostring(strTxt or ""):gsub("%%","%%%%") -- Hide all % symbols
return strTxt
end -- function inert
-- convert is pattern without captures version of string.gsub()
function fh.convert(strTxt,strOld,strNew,intNum)
return tostring(strTxt or ""):gsub(tostring(strOld or ""),function() return strNew end,tonumber(intNum)) -- Hide % capture symbols
end -- function convert
local dicUpper = { }
local dicLower = { }
local dicCaseX = { }
-- ASCII unaccented letter translations for Upper, Lower, and Case Insensitive
for intUpper = string.byte("A"), string.byte("Z") do
local strUpper = string.char(intUpper)
local strLower = string.char(intUpper - string.byte("A") + string.byte("a"))
dicUpper[strLower] = strUpper
dicLower[strUpper] = strLower
local strCaseX = "["..strUpper..strLower.."]"
dicCaseX[strLower] = strCaseX
dicCaseX[strUpper] = strCaseX
end
-- Supply character length of ANSI text --
function fh.length(strTxt)
return string.len(strTxt or "")
end -- function length
-- Supply character substring of ANSI text --
function fh.substring(strTxt,i,j)
return string.sub(strTxt or "",i,j)
end -- function substring
-- Translate upper/lower case ANSI letters to pattern that matches both --
function fh.caseless(strTxt)
strTxt = tostring(strTxt or ""):gsub("[A-Za-z]",dicCaseX)
return strTxt
end -- function caseless
if fh.encoding() == "UTF-8" then
-- Supply character length of UTF-8 text --
function fh.length(strTxt)
isFlag = fhIsConversionLossFlagSet()
strTxt = fhConvertUTF8toANSI(strTxt or "")
fhSetConversionLossFlag(isFlag)
return string.len(strTxt)
end -- function length
local strUTF8 = "([%z\1-\127\194-\244][\128-\191]*)" -- Cater for Lua 5.1 %z or Lua 5.3 \0
if fhGetAppVersion() > 6 then
strUTF8 = "([\0-\127\194-\244][\128-\191]*)"
end
-- Supply character substring of UTF-8 text --
function fh.substring(strTxt,i,j)
local strSub = ""
j = j or -1
if j < 0 then j = j + length(strTxt) + 1 end
if i < 0 then i = i + length(strTxt) + 1 end
for strChr in string.gmatch(strTxt or "",strUTF8) do
if j <= 0 then break end
j = j - 1
i = i - 1
if i <= 0 then strSub = strSub..strChr end
end
return strSub
end -- function substring
-- Translate lower case to upper case UTF-8 letters --
function fh.upper(strTxt)
strTxt = tostring(strTxt or ""):gsub("([a-z\194-\244][\128-\191]*)",dicUpper)
return strTxt
end -- function upper
-- Translate upper case to lower case UTF-8 letters --
function fh.lower(strTxt)
strTxt = tostring(strTxt or ""):gsub("([A-Z\194-\244][\128-\191]*)",dicLower)
return strTxt
end -- function lower
-- Translate upper/lower case UTF-8 letters to pattern that matches both --
function fh.caseless(strTxt)
strTxt = tostring(strTxt or ""):gsub("([A-Za-z\194-\244][\128-\191]*)",dicCaseX)
return strTxt
end -- function caseless
-- Following tables use ASCII numeric coding to be immune from ANSI/UTF-8 encoding --
local arrPairs = -- Upper & Lower case groups of UTF-8 letters with same prefix --
{-- { Prefix; Beg ; End ; Inc; Offset Upper > Lower }; -- These include all ANSI letters and many more
{ "\195"; 0x80; 0x96; 1 ; 32 }; -- 195=0xC3 À U+00C0 to Ö U+00D6 and à U+00E0 to ö U+00F6
{ "\195"; 0x98; 0x9E; 1 ; 32 }; -- 195=0xC3 Ø U+00D8 to Þ U+00DE and ø U+00F8 to þ U+00FE
{ "\196"; 0x80; 0xB6; 2 ; 1 }; -- 196=0xC4 A U+0100 to k U+0137 in pairs
{ "\196"; 0xB9; 0xBD; 2 ; 1 }; -- 196=0xC4 L U+0139 to l U+013E in pairs
{ "\197"; 0x81; 0x87; 2 ; 1 }; -- 197=0xC5 L U+0141 to n U+0148 in pairs
{ "\197"; 0x8A; 0xB6; 2 ; 1 }; -- 197=0xC5 ? U+014A to y U+0177 in pairs
{ "\197"; 0xB9; 0xBD; 2 ; 1 }; -- 197=0xC5 Z U+0179 to ž U+017E in pairs
{ "\198"; 0x82; 0x84; 2 ; 1 }; -- 198=0xC6 ? U+0182 to ? U+0185 in pairs
-- Add more Unicode groups here as usage increases --
}
local dicPairs = -- Upper v Lower case UTF-8 letters that don't fit groups above --
{ [string.char(0xC4,0xBF)] = string.char(0xC5,0x80); -- ? U+013F and ? U+0140
[string.char(0xC5,0xB8)] = string.char(0xC3,0xBF); -- Ÿ U+0178 and ÿ U+00FF
}
local intBeg1 = string.byte(string.sub("À",1))
local intBeg2 = string.byte(string.sub("À",2))
local intEnd1 = string.byte(string.sub("Z",1))
local intEnd2 = string.byte(string.sub("Z",2))
-- print(string.format("%#x %#x %#x %#x",intBeg1,intBeg2,intEnd1,intEnd2)) -- Useful to work out numeric coding
-- Populate the UTF-8 letter translation dictionaries --
for intGroup, tblGroup in ipairs ( arrPairs ) do -- UTF-8 accented letter groups
local strPrefix = tblGroup[1]
for intUpper = tblGroup[2], tblGroup[3], tblGroup[4] do
local strUpper = string.char(intUpper)
local strLower = string.char(intUpper + tblGroup[5])
local strCaseX = strPrefix.."["..strUpper..strLower.."]"
strUpper = strPrefix..strUpper
strLower = strPrefix..strLower
dicUpper[strLower] = strUpper
dicLower[strUpper] = strLower
dicCaseX[strLower] = strCaseX
dicCaseX[strUpper] = strCaseX
end
end
for strUpper, strLower in pairs ( dicPairs ) do -- UTF-8 accented letters where upper & lower have different prefix
dicUpper[strLower] = strUpper
dicLower[strUpper] = strLower
local strCaseX = ""
for intByte = 1, #strUpper do -- Matches more than just the two letters, but can't do any better
strCaseX = strCaseX.."["..strUpper:sub(intByte,intByte)..strLower:sub(intByte,intByte).."]"
end
dicCaseX[strLower] = strCaseX
dicCaseX[strUpper] = strCaseX
end
end
-- overload fh functions into string table
for strIndex, anyValue in pairs(fh) do
if type(anyValue) == "function" then
string[strIndex] = anyValue
end
end
return fh
end -- local function stringx_v3
local stringx = stringx_v3() -- To access FH string extension module
--[[
@Module: +fh+iterate_v3
@Author: Mike Tate
@Version: 3.0
@LastUpdated: 25 Aug 2020
@Description: An iterater functions module to supplement LUA functions.
@V3.0: Function Prototype Closure version.
@V1.2: RecordTypes() includes HEAD tag.
@V1.1: ?
@V1.0: Initial version.
]]
local function iterate_v3()
local fh = {} -- Local environment table
-- Iterator for all records of one chosen type --
function fh.Records(strType)
local ptrAll = fhNewItemPtr() -- Pointer to all records in turn
local ptrRec = fhNewItemPtr() -- Pointer to record returned to user
ptrAll:MoveToFirstRecord(strType)
return function ()
ptrRec:MoveTo(ptrAll)
ptrAll:MoveNext()
if ptrRec:IsNotNull() then return ptrRec end
end
end -- function Records
-- Iterator for all the record types --
function fh.RecordTypes()
local intNext = -1 -- Next record type number
local intLast = fhGetRecordTypeCount() -- Last record type number
return function()
intNext = intNext + 1
if intNext == 0 then -- Includes HEAD tag -- V1.2
return "HEAD"
elseif intNext <= intLast then
return fhGetRecordTypeTag(intNext) -- Return record type tag
end
end
end -- function RecordTypes
-- Iterator for all items in all records of chosen types --
function fh.Items(...)
local arg = {...}
local intType = 1 -- Integer record type number
local tblType = {} -- Table of record type tags
local ptrNext = fhNewItemPtr() -- Pointer to next item in turn
local ptrItem = fhNewItemPtr() -- Pointer to item returned to user
if #arg == 0 then
for intType = 1, fhGetRecordTypeCount() do -- No parameters so use all record types
tblType[intType] = fhGetRecordTypeTag(intType)
end
else
tblType = arg -- Got parameters so use them instead
end
-- print(tblType[intType],intType)
ptrNext:MoveToFirstRecord(tblType[intType]) -- Get first record of first type
return function()
repeat
while ptrNext:IsNotNull() do -- Loop through all items
ptrItem:MoveTo(ptrNext)
ptrNext:MoveNextSpecial()
if ptrItem:IsNotNull() then return ptrItem end
end
intType = intType + 1 -- Loop through each record type
if intType <= #tblType then
ptrNext:MoveToFirstRecord(tblType[intType])
end
until intType > #tblType
end
end -- function Items
-- Iterator for all facts of an individual --
function fh.Facts(ptrIndi)
local ptrItem = fhNewItemPtr() -- Pointer to each item at level 1
local ptrFact = fhNewItemPtr() -- Pointer to each fact returned to user
ptrItem:MoveToFirstChildItem(ptrIndi)
return function ()
while ptrItem:IsNotNull() do
ptrFact:MoveTo(ptrItem)
ptrItem:MoveNext()
if fhIsFact(ptrFact) then return ptrFact end
end
end
end -- function Facts
return fh
end -- local function iterate_v3
local iterate = iterate_v3() -- To access FH iterate items module
--[[
@Module: +fh+general_v3
@Author: Mike Tate
@Version: 3.5
@LastUpdated: 12 Dec 2024
@Description: A general functions module to supplement LUA functions, where filenames use UTF-8 but for a few exceptions.
@V3.5: Further fix for Crossover/WINE file attributes;
@V3.4: Further fix for Unix/WINE file attributes;
@V3.3: Fix problem in fh.MakeFolder(...) when folder path is invalid; Remove Type, ShortName & ShortPath from attributes(...) as unsupported in WINE;
@V3.2: Added function DetectOldModules(); Updated functions RenameFile(), RenameFolder() & GetFolderContents();
@V3.1: Functions derived from FH V7 fhFileUtils library using File System Objects, plus additional features;
@V3.0: Function Prototype Closure version; GetDayNumber() error message reasons;
@V1.5: Revised SplitFilename(strFilename) for missing extension.
@V1.4: Revised EstimatedBirthDates() & EstimatedDeathDates() to fix null Dates.
@V1.3: Add GetDayNumber(), EstimatedBirthDates(), EstimatedDeathDates().
@V1.2: SplitFilename() updated for directory only paths, and MakeFolder() added.
@V1.1: pl.path experiment revoked. New DirTree with omit branch option. Avoid using stringx_v2.
@V1.0: Initial version.
]]
local function general_v3()
local fh = {} -- Local environment table
require("luacom") -- To create File System Object
fh.FSO = luacom.CreateObject("Scripting.FileSystemObject")
-- Report error message --
local function doError(strMessage,errFunction)
-- strMessage ~ error message text
-- errFunction ~ optional error reporting function
if type(errFunction) == "function" then
errFunction(strMessage)
else
error(strMessage)
end
end -- local function doError
-- Convert filename to ANSI alternative and indicate success --
function fh.FileNameToANSI(strFileName,strAnsiName)
-- strFileName ~ full file path
-- strAnsiFile ~ ANSI file name & type
-- return values ~ ANSI file path, true if original path was ANSI compatible
if stringx.encoding() == "ANSI" then return strFileName, true end
local isFlag = fhIsConversionLossFlagSet()
fhSetConversionLossFlag(false)
local strAnsi = fhConvertUTF8toANSI(strFileName)
local wasAnsi = true
if fhIsConversionLossFlagSet() then
strAnsiName = strAnsiName or "ANSI.ANSI"
strAnsi = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugin Data\\"..strAnsiName
wasAnsi = false
end
fhSetConversionLossFlag(isFlag)
return strAnsi, wasAnsi
end -- local function FileNameToANSI
-- Get parent folder --
function fh.GetParentFolder(strFileName)
-- strFileName ~ full file path
-- return value ~ parent folder path
local strParent = fh.FSO:GetParentFolderName(strFileName) --! Faulty in FH v6 with Unicode chars in path
if fhGetAppVersion() == 6 then
local _, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
strParent = strFileName:match("^(.+)[\\/][^\\/]+[\\/]?$")
end
end
return strParent
end -- function GetParentFolder
-- Check if file exists --
function fh.FlgFileExists(strFileName)
-- strFileName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FileExists(strFileName)
end -- function FlgFileExists
-- Check if folder exists --
function fh.FlgFolderExists(strFolderName)
-- strFolderName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FolderExists(strFolderName)
end -- function FlgFolderExists
-- Delete a file if it exists --
function fh.DeleteFile(strFileName,errFunction)
-- strFileName ~ full file path
-- errFunction ~ optional error reporting function
-- return value ~ true if file does not exist or is deleted else false
if fh.FSO:FileExists(strFileName) then
fh.FSO:DeleteFile(strFileName,true)
if fh.FSO:FileExists(strFileName) then
doError("File Not Deleted:\n"..strFileName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFile
-- Delete a folder if it exists including contents --
function fh.DeleteFolder(strFolderName,errFunction)
-- strFolderName ~ full folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder does not exist or is deleted else false
if fh.FSO:FolderExists(strFolderName) then
fh.FSO:DeleteFolder(strFolderName,true)
if fh.FSO:FolderExists(strFolderName) then
doError("Folder Not Deleted:\n"..strFolderName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFolder
-- Rename a file if it exists --
function fh.RenameFile(strFileName,strNewName)
-- strFileName ~ full file path
-- strNewName ~ new file name & type
-- return value ~ true if file exists but new name does not and rename is OK else false
local strNewFile = fh.GetParentFolder(strFileName).."\\"..strNewName
if fh.FSO:FileExists(strFileName) and not fh.FSO:FileExists(strNewFile) then
local fileObject = fh.FSO:GetFile(strFileName)
fileObject.Name = strNewName
if fh.FSO:FileExists(strNewFile) then
return true
end
end
return false
end -- function RenameFile
-- Rename a folder if it exists --
function fh.RenameFolder(strFolderName,strNewName)
-- strFolderName ~ full folder path
-- strNewName ~ new folder name
-- return value ~ true if folder exists but new name does not and rename is OK else false
local strNewFolder = fh.GetParentFolder(strFolderName).."\\"..strNewName
if fh.FSO:FolderExists(strFolderName) and not fh.FSO:FolderExists(strNewFolder) then
local folderObject = fh.FSO:GetFolder(strFolderName)
folderObject.Name = strNewName
if fh.FSO:FolderExists(strNewFolder) then
return true
end
end
return false
end -- function RenameFolder
-- Copy a file if it exists and destination is not a folder --
function fh.CopyFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
fh.FSO:CopyFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
return false
end -- function CopyFile
-- Copy a folder if it exists and destination is not a file --
function fh.CopyFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
fh.FSO:CopyFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
return false
end -- function CopyFolder
-- Move a file if it exists and destination is not a folder --
function fh.MoveFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
if fh.DeleteFile(strDestination) then
fh.FSO:MoveFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFile
-- Move a folder if it exists and destination is not a file --
function fh.MoveFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
if fh.DeleteFolder(strDestination) then
fh.FSO:MoveFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFolder
local function CreateFolder(strFolderName) -- V3.3
fh.FSO:CreateFolder(strFolderName)
end -- local function CreateFolder(strFolderName)
-- Make subfolder recursively if does not exist --
function fh.MakeFolder(strFolderName,errFunction)
-- strFolderName ~ full source folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder exists or created else false
if not fh.FSO:FolderExists(strFolderName) then
if #strFolderName > 4 -- V3.3
and not fh.MakeFolder(fh.GetParentFolder(strFolderName),errFunction) then
return false
end
if not pcall(CreateFolder,strFolderName) -- V3.3
or not fh.FSO:FolderExists(strFolderName) then
doError("Cannot Make Folder Path: \n"..strFolderName.." \n",errFunction)
return false
end
end
return true
end -- function MakeFolder
-- Check if folder writable --
function fh.FlgFolderWrite(strFolderName)
-- strFolderName ~ full source folder path
-- return value ~ true if folder writable else false
if fh.FlgFolderExists(strFolderName) then
if fh.MakeFolder(strFolderName.."\\vwxyz") then
fh.FSO:DeleteFolder(strFolderName.."\\vwxyz",true)
return true
end
end
return false
end -- function FlgFolderWrite
-- Open File with ANSI path and return Handle --
function fh.OpenFile(strFileName,strMode)
-- strFileName ~ full file path
-- strMode ~ "r", "w", "a" optionally suffixed with "+" &/or "b"
-- return value ~ file handle
local fileHandle, strError = io.open(strFileName,strMode)
if fileHandle == nil then
error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..strError.." \n")
end
return fileHandle
end -- function OpenFile
-- Save string to file --
function fh.SaveStringToFile(strContents,strFileName,strFormat)
-- strContents ~ text string
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ true if successful else false
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhSaveTextFile(strFileName,strContents,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
local fileHandle = fh.OpenFile(strAnsi,"w")
fileHandle:write(strContents)
assert(fileHandle:close())
if not wasAnsi then
fh.MoveFile(strAnsi,strFileName)
end
return true
end -- function SaveStringToFile
-- Load string from file --
function fh.StrLoadFromFile(strFileName,strFormat)
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ file contents
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhLoadTextFile(strFileName,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
fh.CopyFile(strFileName,strAnsi)
end
local fileHandle = fh.OpenFile(strAnsi,"r")
local strContents = fileHandle:read("*all")
assert(fileHandle:close())
return strContents
end -- function StrLoadFromFile
-- Returns the Path, Filename, and Extension as 3 values --
function fh.SplitFilename(strFileName)
-- strFileName ~ full file path
-- return values ~ path, name.type, type
if fh.FSO:FolderExists(strFileName) then
local strPath = strFileName:gsub("[\\/]$","")
return strPath.."\\","",""
end
strFileName = strFileName.."."
return strFileName:match("^(.-)([^\\/]-%.([^\\/%.]-))%.?$")
end -- function SplitFilename
-- Convert dd/mm/yyyy hh:mm:ss format to integer seconds -- (DateTime format is used in attributes returned by GetFolderContents and DirTree below)
function fh.IntTime(strDateTime)
-- strDateTime ~ date time string
-- return value ~ integer seconds since 01/01/1970 00:00:00
local strDay,strMonth,strYear,strHour,strMin,strSec = strDateTime:match("^(%d%d)/(%d%d)/(%d+) (%d%d):(%d%d):(%d%d)")
if tonumber(strYear) < 1970 then return 0 end
local isDST = false
if tonumber(strMonth) > 4 and tonumber(strMonth) < 11 then isDST = true end -- Approximation is sometimes wrong
local intTime = os.time( { year=strYear; month=strMonth; day=strDay; hour=strHour; min=strMin; sec=strSec; isdst=isDST; } )
local tblDat = os.date("*t",intTime)
if tblDat.isdst then
intTime = intTime + 3600
isDST = true
end
return intTime
end -- function IntTime
-- Return table of attributes for folder --
local function attribFolder(tblAttr)
-- tblAttr ~ folder attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE only supports the tblAttr.name & tblAttr.path
return { mode="directory"; name=tblAttr.name; path=tblAttr.path; }
end -- local function attribFolder
-- Return table of attributes for file --
local function attribFile(tblAttr)
-- tblAttr ~ file attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE does not support the tblAttr.type, tblAttr.shortname, tblAttr.shortpath & sometimes tblAttr.datecreated
return { mode="file"; name=tblAttr.name; path=tblAttr.path; size=tblAttr.size; modified=tblAttr.datelastmodified; attributes=tblAttr.attributes; }
end -- local function attribFile
-- Return attributes table of all files and folders in a specified folder --
function fh.GetFolderContents(strFolder,doRecurse)
-- strFolder ~ full folder path
-- doRecurse ~ true for recursion
-- return value ~ attributes table
local arrList = {}
if fh.FSO:FolderExists(strFolder) then
local function getFileList(strFolder)
local tblList = fh.FSO:GetFolder(strFolder)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFolder(tblAttr))
if doRecurse then getFileList(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFile(tblAttr))
tblAttr = tblEnum:Next()
end
end
getFileList(strFolder)
end
return arrList
end -- function GetFolderContents
-- Return a Directory Tree entry & attributes on each iteration --
function fh.DirTree(strDir,...)
-- strDir ~ full folder path
-- ... ~ list of folders to omit
-- return value ~ full path, attributes table
local arg = {...}
assert( fh.FSO:FolderExists(strDir), "directory parameter is missing or empty" )
local function yieldtree(strDir)
local tblList = fh.FSO:GetFolder(strDir)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.SubFolders) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"directory"))
local isOK = true
for _,strOmit in ipairs (arg) do
if tblAttr.path:match(strOmit) then -- Omit tree branch
isOK = false
break
end
end
if isOK then yieldtree(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.Files) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"file"))
tblAttr = tblEnum:Next()
end
end
return coroutine.wrap(function() yieldtree(strDir) end)
end -- function DirTree
-- Detect FH V5/6 old library modules and advise removal --
function fh.DetectOldModules()
if fhGetAppVersion() > 6 then
local strPath = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugins\\"
local arrFile = { "compat53.lua"; "ltn12.lua"; "luasql\\sqlite3.dll"; "md5.lua"; "pl\\init.lua"; "socket.lua"; "utf8.lua"; "zip.dll"; }
for _, strFile in ipairs (arrFile) do
if fh.FSO:FileExists(strPath..strFile) then
fhMessageBox("\n Detected some old FH V6 library modules. \n\nPlease remove them by running the plugin: \n\n 'Delete old FH6 Plugin Module Files' \n","MB_OK","MB_ICONEXCLAMATION")
break
end
end
end
end -- function DetectOldModules
if fhGetAppVersion() > 6 then unpack = table.unpack end
-- Invoke FH Shell Execute API --
function fh.DoExecute(strExecutable,...)
-- strExecutable ~ full path of executable
-- ... ~ parameter list and optional error reporting function
-- return value ~ true if successful else false
local arg = {...}
local errFunction = fhMessageBox
if type(arg[#arg]) == 'function' then
errFunction = arg[#arg]
table.remove(arg)
end
local isOK, intErrorCode, strErrorText = fhShellExecute(strExecutable,unpack(arg))
if not isOK then
errFunction(tostring(strErrorText).." ("..tostring(intErrorCode)..")")
end
return isOK
end -- function DoExecute
-- Obtain the Day Number for any Date Point -- -- Fix problems with invalid dates in DayNumber function
function fh.GetDayNumber(datDate)
-- datDate ~ date point
-- return value ~ day number
if datDate:IsNull() then return 0 end
local intDay = fhCallBuiltInFunction("DayNumber",datDate) -- Only works for Gregorian dates that were not skipped nor BC dates
if not intDay then
local strError = "because " -- Error message reason -- V3.0
local calendar = datDate:GetCalendar()
local oldMonth = datDate:GetMonth()
local oldDayNo = datDate:GetDay()
local intMonth = math.min( oldMonth, 12 ) -- Limit month to 12, and day to last of each month
local intDayNo = math.min( oldDayNo, ({0;31;28;31;30;31;30;31;31;30;31;30;31;})[intMonth+1] )
local intYear = datDate:GetYear()
if oldDayNo > intDayNo then strError = strError.."day "..oldDayNo.." too big " end
if oldMonth > intMonth then strError = strError.."month "..oldMonth.." too big " end
if calendar == "Hebrew" and intYear > 3761 then
intYear = intYear - 3761
strError = strError.."Hebrew year > 3761 "
elseif calendar ~= "Gregorian" then
strError = strError..calendar.." disallowed "
end
if intYear == 1752 and intMonth == 9 and intDayNo <= 13 then -- Use 2 Sep 1752 for 3 - 13 Sep 1752 dates skipped
intDayNo = 2
strError = strError.."3 - 13 Sep 1752 skipped "
elseif intYear == 1582 and intMonth == 10 and intDayNo <= 14 then -- Use 4 Oct 1582 for 5 - 14 Oct 1582 dates skipped
intDayNo = 4
strError = strError.."5 - 14 Oct 1582 skipped "
end
local setDate = fhNewDatePt(intYear,intMonth,intDayNo,datDate:GetYearDD())
intDay = fhCallBuiltInFunction("DayNumber",setDate) -- Remove BC and Julian, Hebrew, French calendars
if not intDay then intDay = 0 end
local oldDate = fhNewDate() oldDate:SetSimpleDate(datDate) -- Report problem to user
local newDate = fhNewDate() newDate:SetSimpleDate(setDate)
local strIsBC = ""
if datDate:GetBC() then
strError = strError.." B.C. disallowed "
intDay = -intDay
strIsBC = "and Day Number negated"
end
fhMessageBox("\n Get Day Number issue for date \n "..oldDate:GetDisplayText().." \n "..strError.." \n So replaced it with date \n "..newDate:GetDisplayText().." \n "..strIsBC,"MB_OK","MB_ICONEXCLAMATION")
end
return intDay
end -- function GetDayNumber
local dtpYearMin = fhNewDatePt(1000) -- Minimum year to use when earliest estimate is null
local dtpYearMax = fhNewDatePt(2000) -- Maximum year to use when latest estimate is null
function fh.GetYearToday() -- Get the Year for Today
-- return value ~ integer year today
local intYearToday = fhCallBuiltInFunction("Year",fhCallBuiltInFunction("Today"))
dtpYearMax = fhNewDatePt(intYearToday) -- Set maximum year date point
return intYearToday
end -- function GetYearToday()
local function getDeathFacts(ptrIndi) -- Iterate Death, Burial, Cremation facts
-- ptrIndi ~ pointer to individual
-- return value ~ pointer to fact
local arrFact = { "~.DEAT"; "~.BURI"; "~.CREM"; }
local intFact = 0
local ptrFact = fhNewItemPtr() -- Pointer to each fact returned to user
return function ()
while intFact < #arrFact do
intFact = intFact + 1
ptrFact = fhGetItemPtr(ptrIndi,arrFact[intFact])
if ptrFact:IsNotNull() then return ptrFact end
end
end
end -- local function getDeathFacts
-- Ensure Estimated Date EARLIEST <= LATEST <= Fact Date -- -- Fix errors in EstimatedBirth/DeathDate function
local function estimatedDates(strFunc,ptrIndi,intGens,getFact,intYrs)
-- strFunc ~ "EstimatedBirthDate" or "EstimatedDeathDate"
-- ptrIndi ~ Individual of interest
-- intGens ~ Number of generations (may be nil)
-- getFact ~ Iterator function for facts
-- intYrs ~ Years to add to After dates
-- return values ~ EARLIEST, MID, LATEST dates
intGens = intGens or 2
local dtpMin = fhCallBuiltInFunction(strFunc,ptrIndi,"EARLIEST",intGens)
local dtpMax = fhCallBuiltInFunction(strFunc,ptrIndi,"LATEST",intGens)
local dtpMid = fhNewDatePt()
if not ( dtpMin:IsNull() and dtpMax:IsNull() ) then -- Skip if both null
if dtpMax:IsNull() then dtpMax = dtpYearMax elseif dtpMin:IsNull() then dtpMin = dtpYearMin end
for ptrFact in getFact(ptrIndi) do
local datFact = fhGetValueAsDate(fhGetItemPtr(ptrFact,"~.DATE"))
if not datFact:IsNull() then -- Find 1st Fact Date
local dtpLast = datFact:GetDatePt1() -- Last date = DatePt1 for Simple, Range, and Before
local strType = datFact:GetSubtype() -- Between = DatePt2 and After = DatePt1 + intYrs
if strType == "Between" then dtpLast = datFact:GetDatePt2()
elseif strType == "After" then dtpLast = fhNewDatePt(dtpLast:GetYear()+intYrs,dtpLast:GetMonth(),dtpLast:GetDay()) end -- Compare only uses Year, Month, Day so omitted ,dtpLast:GetYearDD(),dtpLast:GetBC(),dtpLast:GetCalendar()
if dtpMax:Compare(dtpLast) > 0 then dtpMax = dtpLast end
if dtpMin:Compare(dtpMax) > 0 then dtpMin = dtpMax end
if strType ~= "After" then break end -- Now EARLIEST <= LATEST <= Last date
end
end
local intDays = ( fh.GetDayNumber(dtpMax) - fh.GetDayNumber(dtpMin) ) / 2
local intYear,remYear = math.modf( intDays / 365.2422 ) -- Offset year @ 365.2422 days per year, and remainder fraction
local intMnth = math.floor( ( remYear * 12 ) + 0.1 ) -- Offset month is remainder fraction of year * 12
dtpMid = fhCallBuiltInFunction("CalcDate",dtpMin,intYear,intMnth) -- Need approximate MID year & month
end
return { Min=dtpMin; Mid=dtpMid; Max=dtpMax; } -- Return EARLIEST, MID, LATEST dates
end -- local function estimatedDates
-- Make EstimatedBirthDate EARLIEST <= LATEST <= 1st Fact Date -- -- Fix errors in EstimatedBirthDate function
function fh.EstimatedBirthDates(ptrIndi,intGens)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
return estimatedDates("EstimatedBirthDate",ptrIndi,intGens,iterate.Facts,10)
end -- function EstimatedBirthDates
-- Make EstimatedDeathDate EARLIEST <= LATEST <= DEAT/BURI/CREM Date -- -- Fix errors in EstimatedDeathDate function
function fh.EstimatedDeathDates(ptrIndi,intGens)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
return estimatedDates("EstimatedDeathDate",ptrIndi,intGens,getDeathFacts,100)
end -- function EstimatedDeathDates
--[[
@function: BuildDataRef
@description: Get Full Data Reference for Pointer
@parameters: Item Pointer
@returns: Data Reference String, Record Id Integer, Record Type Tag String
@requires: None
]]
function fh.BuildDataRef(ptrRef)
local strDataRef = "" -- Data Reference with instance indices e.g. INDI.RESI[2].ADDR
local intRecId = 0 -- Record Id for associated Record
local strRecTag = "" -- Record Tag of associated Record type i.e. INDI, FAM, NOTE, SOUR, etc
-- getDataRef() is called recursively per level of the Data Ref
-- ptrRef points to the upper Data Ref levels yet to be analysed
-- strRef compiles the lower Data Ref levels including instances
local function getDataRef(ptrRef,strRef)
local ptrTag = ptrRef:Clone()
local strTag = fhGetTag(ptrTag) -- Current level Tag
ptrTag:MoveToParentItem(ptrTag)
if ptrTag:IsNotNull() then -- Parent level exists
local intSib = 1
local ptrSib = ptrRef:Clone() -- Pointer to siblings with same Tag
ptrSib:MovePrev("SAME_TAG")
while ptrSib:IsNotNull() do -- Count previous siblings with same Tag
intSib = intSib + 1
ptrSib:MovePrev("SAME_TAG")
end
if intSib > 1 then strTag = strTag.."["..intSib.."]" end
getDataRef(ptrTag,"."..strTag..strRef) -- Now analyse the parent level
else
strDataRef = strTag..strRef -- Record level reached, so set return values
intRecId = fhGetRecordId(ptrRef)
strRecTag = strTag
if not fhIsValidDataRef(strDataRef) then print("BuildDataRef: "..strDataRef.." is Invalid") end
end
end -- local function getDataRef
if type(ptrRef) == "userdata" then getDataRef(ptrRef,"") end
return strDataRef, intRecId, strRecTag
end -- function BuildDataRef
--[[
@function: GetDataRefPtr
@description: Get Pointer for Full Data Reference
@parameters: Data Reference String, Record Id Integer, Record Type Tag String (optional)
@returns: Item Pointer which IsNull() if any parameters are invalid
@requires: None
]]
function fh.GetDataRefPtr(strDataRef,intRecId,strRecTag)
strDataRef = strDataRef or ""
if not strRecTag then
strRecTag = strDataRef:gsub("^(%u+).*$","%1") -- Extract Record Tag from Data Ref
end
local ptrRef = fhNewItemPtr()
ptrRef:MoveToRecordById(strRecTag,intRecId or 0) -- Lookup the Record by Id
ptrRef:MoveTo(ptrRef,strDataRef) -- Move to the Data Ref
return ptrRef
end -- function GetDataRefPtr
function fh.TblDataRef(ptrRef)
local tblRef = {}
tblRef.DataRef, tblRef.RecId, tblRef.RecTag = BuildDataRef(ptrRef)
return tblRef
end -- function TblDataRef
function fh.PtrDataRef(tblRef)
local tblRef = tblRef or {} -- Ensure table and its fields exist
return GetDataRefPtr(tblRef.DataRef or "",tblRef.RecId or 0,tblRef.RecTag or "")
end -- function PtrDataRef
return fh
end -- local function general_v3
local general = general_v3() -- To access FH general tools module
--[[
@Module: +fh+tablex_v3
@Author: Mike Tate
@Version: 3.1
@LastUpdated: 08 Jan 2022
@Description: A Table Load Save Module.
@V3.1: Cater for full UTF-8 filenames.
@V3.0: Function Prototype Closure version.
@V1.2: Added local definitions of _ to ensure nil gets returned on error.
@V1.1: ?
@V1.0: Initial version 0.94 is Lua 5.1 compatible.
]]
local function tablex_v3()
local fh = {} -- Local environment table
------------------------------------------------------ Start Table Load Save
-- require "_tableloadsave"
--[[
Save Table to File/Stringtable
Load Table from File/Stringtable
v 0.94
Lua 5.1 compatible
Userdata and indices of these are not saved
Functions are saved via string.dump, so make sure it has no upvalues
References are saved
----------------------------------------------------
table.save( table [, filename] )
Saves a table so it can be called via the table.load function again
table must a object of type 'table'
filename is optional, and may be a string representing a filename or true/1
table.save( table )
on success: returns a string representing the table (stringtable)
(uses a string as buffer, ideal for smaller tables)
table.save( table, true or 1 )
on success: returns a string representing the table (stringtable)
(uses io.tmpfile() as buffer, ideal for bigger tables)
table.save( table, "filename" )
on success: returns 1
(saves the table to file "filename")
on failure: returns as second argument an error msg
----------------------------------------------------
table.load( filename or stringtable )
Loads a table that has been saved via the table.save function
on success: returns a previously saved table
on failure: returns as second argument an error msg
----------------------------------------------------
chillcode, http://lua-users.org/wiki/SaveTableToFile
Licensed under the same terms as Lua itself.
]]--
-- declare local variables
--// exportstring( string )
--// returns a "Lua" portable version of the string
local function exportstring( s )
s = string.format( "%q",s )
-- to replace
s = string.gsub( s,"\\\n","\\n" )
s = string.gsub( s,"\r","\\r" )
s = string.gsub( s,string.char(26),"\"..string.char(26)..\"" )
return s
end
--// The Save Function
function fh.save( tbl,filename )
local charS,charE = " ","\n"
local file,err,_,stransi,wasansi -- V1.2 -- V3.1 -- Added _,stransi,wasansi --!
-- create a pseudo file that writes to a string and return the string
if not filename then
file = { write = function( self,newstr ) self.str = self.str..newstr end, str = "" }
charS,charE = "",""
-- write table to tmpfile
elseif filename == true or filename == 1 then
charS,charE,file = "","",io.tmpfile()
-- write table to file
-- use io.open here rather than io.output, since in windows when clicking on a file opened with io.output will create an error
else
stransi,wasansi = general.FileNameToANSI(filename) -- V3.1 -- Cater for non-ANSI filename --!
file,err = io.open( stransi, "w" )
if err then return _,err end
end
-- initiate variables for save procedure
local tables,lookup = { tbl },{ [tbl] = 1 }
file:write( "return {"..charE )
for idx,t in ipairs( tables ) do
if filename and filename ~= true and filename ~= 1 then
file:write( "-- Table: {"..idx.."}"..charE )
end
file:write( "{"..charE )
local thandled = {}
for i,v in ipairs( t ) do
thandled[i] = true
-- escape functions and userdata
if type( v ) ~= "userdata" then
-- only handle value
if type( v ) == "table" then
if not lookup[v] then
table.insert( tables, v )
lookup[v] = #tables
end
file:write( charS.."{"..lookup[v].."},"..charE )
elseif type( v ) == "function" then
file:write( charS.."loadstring("..exportstring(string.dump( v )).."),"..charE )
else
local value = ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
file:write( charS..value..","..charE )
end
end
end
for i,v in pairs( t ) do
-- escape functions and userdata
if (not thandled[i]) and type( v ) ~= "userdata" then
-- handle index
if type( i ) == "table" then
if not lookup[i] then
table.insert( tables,i )
lookup[i] = #tables
end
file:write( charS.."[{"..lookup[i].."}]=" )
else
local index = ( type( i ) == "string" and "["..exportstring( i ).."]" ) or string.format( "[%d]",i )
file:write( charS..index.."=" )
end
-- handle value
if type( v ) == "table" then
if not lookup[v] then
table.insert( tables,v )
lookup[v] = #tables
end
file:write( "{"..lookup[v].."},"..charE )
elseif type( v ) == "function" then
file:write( "loadstring("..exportstring(string.dump( v )).."),"..charE )
else
local value = ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
file:write( value..","..charE )
end
end
end
file:write( "},"..charE )
end
file:write( "}" )
-- Return Values
-- return stringtable from string
if not filename then
-- set marker for stringtable
return file.str.."--|"
-- return stringttable from file
elseif filename == true or filename == 1 then
file:seek ( "set" )
-- no need to close file, it gets closed and removed automatically
-- set marker for stringtable
return file:read( "*a" ).."--|"
-- close file and return 1
else
file:close()
if not ( wasansi ) then -- V3.1 -- Cater for non-ANSI filename --!
general.MoveFile(stransi,filename)
end
return 1
end
end
--// The Load Function
function fh.load( sfile )
local tables,err,_ -- V1.2 -- Added _
-- catch marker for stringtable
if string.sub( sfile,-3,-1 ) == "--|" then
tables,err = loadstring( sfile )
else
local stransi,wasansi = general.FileNameToANSI(sfile) -- V3.1 -- Cater for non-ANSI filename --!
if not ( wasansi ) then
general.CopyFile(sfile,stransi)
end
tables,err = loadfile( stransi )
if not ( wasansi ) then
general.DeleteFile(stransi) -- V3.1 -- Cater for non-ANSI filename --!
end
end
if err then return _,err
end
tables = tables()
for idx = 1,#tables do
local tolinkv,tolinki = {},{}
for i,v in pairs( tables[idx] ) do
if type( v ) == "table" and tables[v[1]] then
table.insert( tolinkv,{ i,tables[v[1]] } )
end
if type( i ) == "table" and tables[i[1]] then
table.insert( tolinki,{ i,tables[i[1]] } )
end
end
-- link values, first due to possible changes of indices
for _,v in ipairs( tolinkv ) do
tables[idx][v[1]] = v[2]
end
-- link indices
for _,v in ipairs( tolinki ) do
tables[idx][v[2]],tables[idx][v[1]] = tables[idx][v[1]],nil
end
end
return tables[1]
end
------------------------------------------------------ End Table Load Save
-- overload fh functions into table
for strIndex, anyValue in pairs(fh) do
if type(anyValue) == "function" then
table[strIndex] = anyValue
end
end
return fh
end -- local function tablex_v3
local tablex = tablex_v3 () -- To access FH table extension module
--[[
@Module: +fh+encoder_v3
@Author: Mike Tate
@Version: 3.6
@LastUpdated: 27 Aug 2024
@Description: Text encoder module for HTML XHTML XML URI UTF8 UTF16 ISO CP1252/ANSI character codings.
@V3.6: In fh.FileLines(...) cater for empty file;
@V3.5: Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility.
@V3.4: Ensure expressions involving gsub return just text parameter.
@V3.3: Adds UNICODE U+10000 to U+10FFFF UTF-16 Supplementary Planes.
@V3.2: Update for ANSI & Unicode to ASCII for sorting, Soundex, etc.
@V3.1: Update for Unicode UTF-16 & UTF-8 and fhConvertANSItoUTF8 & fhConvertUTF8toANSI, name change UTF to UTF8 & CP to ANSI.
@V2.0: StrUTF8_Encode() replaced by StrUTF_CP1252() for entire UTF-8 range, plus new StrCP1252_ISO().
@V1.0: Initial version.
]]
local function encoder_v3()
local fh = {} -- Local environment table
local fhVersion = fhGetAppVersion()
local br_Tag = "
" -- Markup language break tag default
local br_Lua = "
" -- Lua pattern for break tag recognition
local tblCodePage = {} -- Code Page to XML/XHTML/HTML/URI/UTF8 encodings: http://en.wikipedia.org/wiki/Windows-1252 & 1250 & etc
-- Control characters "\000" to "\031" for URI & Markup "[%c]" encodings are disallowed except for "\t" to "\r"
tblCodePage["\000"] = "" -- NUL
tblCodePage["\001"] = "" -- SOH
tblCodePage["\002"] = "" -- STX
tblCodePage["\003"] = "" -- ETX
tblCodePage["\004"] = "" -- EOT
tblCodePage["\005"] = "" -- ENQ
tblCodePage["\006"] = "" -- ACK
tblCodePage["\a"] = "" -- BEL
tblCodePage["\b"] = "" -- BS
tblCodePage["\t"] = "+" -- HT space in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["\n"] = "%0A" -- LF br_Tag in Markup
tblCodePage["\v"] = "%0A" -- VT br_Tag in Markup
tblCodePage["\f"] = "%0A" -- FF br_Tag in Markup
tblCodePage["\r"] = "%0D" -- CR br_Tag in Markup
tblCodePage["\014"] = "" -- SO
tblCodePage["\015"] = "" -- SI
tblCodePage["\016"] = "" -- DLE
tblCodePage["\017"] = "" -- DC1
tblCodePage["\018"] = "" -- DC2
tblCodePage["\019"] = "" -- DC3
tblCodePage["\020"] = "" -- DC4
tblCodePage["\021"] = "" -- NAK
tblCodePage["\022"] = "" -- SYN
tblCodePage["\023"] = "" -- ETB
tblCodePage["\024"] = "" -- CAN
tblCodePage["\025"] = "" -- EM
tblCodePage["\026"] = "" -- SUB
tblCodePage["\027"] = "" -- ESC
tblCodePage["\028"] = "" -- FS
tblCodePage["\029"] = "" -- GS
tblCodePage["\030"] = "" -- RS
tblCodePage["\031"] = "" -- US
-- ASCII characters "\032" to "\127" for URI "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding
tblCodePage[" "] = "+" -- or "%20" Space
tblCodePage["!"] = "%21" -- Reserved character
tblCodePage['"'] = "%22" -- """ in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["#"] = "%23" -- Reserved character
tblCodePage["$"] = "%24" -- Reserved character
tblCodePage["%"] = "%25" -- Must be encoded
tblCodePage["&"] = "%26" -- Reserved character -- "&" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["'"] = "%27" -- Reserved character -- "'" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["("] = "%28" -- Reserved character
tblCodePage[")"] = "%29" -- Reserved character
tblCodePage["*"] = "%2A" -- Reserved character
tblCodePage["+"] = "%2B" -- Reserved character
tblCodePage[","] = "%2C" -- Reserved character
-- tblCodePage["-"] = "%2D" -- Unreserved character not encoded
-- tblCodePage["."] = "%2E" -- Unreserved character not encoded
tblCodePage["/"] = "%2F" -- Reserved character
-- Digits 0 to 9 -- Unreserved characters not encoded
tblCodePage[":"] = "%3A" -- Reserved character
tblCodePage[";"] = "%3B" -- Reserved character
tblCodePage["<"] = "%3C" -- "<" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["="] = "%3D" -- Reserved character
tblCodePage[">"] = "%3E" -- ">" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["?"] = "%3F" -- Reserved character
tblCodePage["@"] = "%40" -- Reserved character
-- Letters A to Z -- Unreserved characters not encoded
tblCodePage["["] = "%5B" -- Reserved character
tblCodePage["\\"]= "%5C"
tblCodePage["]"] = "%5D" -- Reserved character
tblCodePage["^"] = "%5E"
-- tblCodePage["_"] = "%5F" -- Unreserved character not encoded
tblCodePage["`"] = "%60"
-- Letters a to z -- Unreserved characters not encoded
tblCodePage["{"] = "%7B"
tblCodePage["|"] = "%7C"
tblCodePage["}"] = "%7D"
-- tblCodePage["~"] = "%7E" -- Unreserved character not encoded
tblCodePage["\127"] = "" -- DEL
-- Code Page 1252 Unicode characters "\128" to "\255" for UTF-8 scheme "[€-ÿ]" encodings: http://en.wikipedia.org/wiki/UTF-8
tblCodePage["€"] = string.char(0xE2,0x82,0xAC) -- "€"
tblCodePage["\129"] = "" -- Undefined
tblCodePage["‚"] = string.char(0xE2,0x80,0x9A)
tblCodePage["ƒ"] = string.char(0xC6,0x92)
tblCodePage["„"] = string.char(0xE2,0x80,0x9E)
tblCodePage["…"] = string.char(0xE2,0x80,0xA6)
tblCodePage["†"] = string.char(0xE2,0x80,0xA0)
tblCodePage["‡"] = string.char(0xE2,0x80,0xA1)
tblCodePage["ˆ"] = string.char(0xCB,0x86)
tblCodePage["‰"] = string.char(0xE2,0x80,0xB0)
tblCodePage["Š"] = string.char(0xC5,0xA0)
tblCodePage["‹"] = string.char(0xE2,0x80,0xB9)
tblCodePage["Œ"] = string.char(0xC5,0x92)
tblCodePage["\141"] = "" -- Undefined
tblCodePage["Ž"] = string.char(0xC5,0xBD)
tblCodePage["\143"] = "" -- Undefined
tblCodePage["\144"] = "" -- Undefined
tblCodePage["‘"] = string.char(0xE2,0x80,0x98)
tblCodePage["’"] = string.char(0xE2,0x80,0x99)
tblCodePage["“"] = string.char(0xE2,0x80,0x9C)
tblCodePage["”"] = string.char(0xE2,0x80,0x9D)
tblCodePage["•"] = string.char(0xE2,0x80,0xA2)
tblCodePage["–"] = string.char(0xE2,0x80,0x93)
tblCodePage["—"] = string.char(0xE2,0x80,0x94)
tblCodePage["\152"] = string.char(0xCB,0x9C) -- Small Tilde
tblCodePage["™"] = string.char(0xE2,0x84,0xA2)
tblCodePage["š"] = string.char(0xC5,0xA1)
tblCodePage["›"] = string.char(0xE2,0x80,0xBA)
tblCodePage["œ"] = string.char(0xC5,0x93)
tblCodePage["\157"] = "" -- Undefined
tblCodePage["ž"] = string.char(0xC5,0xBE)
tblCodePage["Ÿ"] = string.char(0xC5,0xB8)
tblCodePage["\160"] = string.char(0xC2,0xA0) -- " " No Break Space
tblCodePage["¡"] = string.char(0xC2,0xA1) -- "¡"
tblCodePage["¢"] = string.char(0xC2,0xA2) -- "¢"
tblCodePage["£"] = string.char(0xC2,0xA3) -- "£"
tblCodePage["¤"] = string.char(0xC2,0xA4) -- "¤"
tblCodePage["¥"] = string.char(0xC2,0xA5) -- "¥"
tblCodePage["¦"] = string.char(0xC2,0xA6)
tblCodePage["§"] = string.char(0xC2,0xA7)
tblCodePage["¨"] = string.char(0xC2,0xA8)
tblCodePage["©"] = string.char(0xC2,0xA9)
tblCodePage["ª"] = string.char(0xC2,0xAA)
tblCodePage["«"] = string.char(0xC2,0xAB)
tblCodePage["¬"] = string.char(0xC2,0xAC)
tblCodePage[""] = string.char(0xC2,0xAD) -- "" Soft Hyphen
tblCodePage["®"] = string.char(0xC2,0xAE)
tblCodePage["¯"] = string.char(0xC2,0xAF)
tblCodePage["°"] = string.char(0xC2,0xB0)
tblCodePage["±"] = string.char(0xC2,0xB1)
tblCodePage["²"] = string.char(0xC2,0xB2)
tblCodePage["³"] = string.char(0xC2,0xB3)
tblCodePage["´"] = string.char(0xC2,0xB4)
tblCodePage["µ"] = string.char(0xC2,0xB5)
tblCodePage["¶"] = string.char(0xC2,0xB6)
tblCodePage["·"] = string.char(0xC2,0xB7)
tblCodePage["¸"] = string.char(0xC2,0xB8)
tblCodePage["¹"] = string.char(0xC2,0xB9)
tblCodePage["º"] = string.char(0xC2,0xBA)
tblCodePage["»"] = string.char(0xC2,0xBB)
tblCodePage["¼"] = string.char(0xC2,0xBC)
tblCodePage["½"] = string.char(0xC2,0xBD)
tblCodePage["¾"] = string.char(0xC2,0xBE)
tblCodePage["¿"] = string.char(0xC2,0xBF)
tblCodePage["À"] = string.char(0xC3,0x80)
tblCodePage["Á"] = string.char(0xC3,0x81)
tblCodePage["Â"] = string.char(0xC3,0x82)
tblCodePage["Ã"] = string.char(0xC3,0x83)
tblCodePage["Ä"] = string.char(0xC3,0x84)
tblCodePage["Å"] = string.char(0xC3,0x85)
tblCodePage["Æ"] = string.char(0xC3,0x86)
tblCodePage["Ç"] = string.char(0xC3,0x87)
tblCodePage["È"] = string.char(0xC3,0x88)
tblCodePage["É"] = string.char(0xC3,0x89)
tblCodePage["Ê"] = string.char(0xC3,0x8A)
tblCodePage["Ë"] = string.char(0xC3,0x8B)
tblCodePage["Ì"] = string.char(0xC3,0x8C)
tblCodePage["Í"] = string.char(0xC3,0x8D)
tblCodePage["Î"] = string.char(0xC3,0x8E)
tblCodePage["Ï"] = string.char(0xC3,0x8F)
tblCodePage["Ð"] = string.char(0xC3,0x90)
tblCodePage["Ñ"] = string.char(0xC3,0x91)
tblCodePage["Ò"] = string.char(0xC3,0x92)
tblCodePage["Ó"] = string.char(0xC3,0x93)
tblCodePage["Ô"] = string.char(0xC3,0x94)
tblCodePage["Õ"] = string.char(0xC3,0x95)
tblCodePage["Ö"] = string.char(0xC3,0x96)
tblCodePage["×"] = string.char(0xC3,0x97)
tblCodePage["Ø"] = string.char(0xC3,0x98)
tblCodePage["Ù"] = string.char(0xC3,0x99)
tblCodePage["Ú"] = string.char(0xC3,0x9A)
tblCodePage["Û"] = string.char(0xC3,0x9B)
tblCodePage["Ü"] = string.char(0xC3,0x9C)
tblCodePage["Ý"] = string.char(0xC3,0x9D)
tblCodePage["Þ"] = string.char(0xC3,0x9E)
tblCodePage["ß"] = string.char(0xC3,0x9F)
tblCodePage["à"] = string.char(0xC3,0xA0)
tblCodePage["á"] = string.char(0xC3,0xA1)
tblCodePage["â"] = string.char(0xC3,0xA2)
tblCodePage["ã"] = string.char(0xC3,0xA3)
tblCodePage["ä"] = string.char(0xC3,0xA4)
tblCodePage["å"] = string.char(0xC3,0xA5)
tblCodePage["æ"] = string.char(0xC3,0xA6)
tblCodePage["ç"] = string.char(0xC3,0xA7)
tblCodePage["è"] = string.char(0xC3,0xA8)
tblCodePage["é"] = string.char(0xC3,0xA9)
tblCodePage["ê"] = string.char(0xC3,0xAA)
tblCodePage["ë"] = string.char(0xC3,0xAB)
tblCodePage["ì"] = string.char(0xC3,0xAC)
tblCodePage["í"] = string.char(0xC3,0xAD)
tblCodePage["î"] = string.char(0xC3,0xAE)
tblCodePage["ï"] = string.char(0xC3,0xAF)
tblCodePage["ð"] = string.char(0xC3,0xB0)
tblCodePage["ñ"] = string.char(0xC3,0xB1)
tblCodePage["ò"] = string.char(0xC3,0xB2)
tblCodePage["ó"] = string.char(0xC3,0xB3)
tblCodePage["ô"] = string.char(0xC3,0xB4)
tblCodePage["õ"] = string.char(0xC3,0xB5)
tblCodePage["ö"] = string.char(0xC3,0xB6)
tblCodePage["÷"] = string.char(0xC3,0xB7)
tblCodePage["ø"] = string.char(0xC3,0xB8)
tblCodePage["ù"] = string.char(0xC3,0xB9)
tblCodePage["ú"] = string.char(0xC3,0xBA)
tblCodePage["û"] = string.char(0xC3,0xBB)
tblCodePage["ü"] = string.char(0xC3,0xBC)
tblCodePage["ý"] = string.char(0xC3,0xBD)
tblCodePage["þ"] = string.char(0xC3,0xBE)
tblCodePage["ÿ"] = string.char(0xC3,0xBF)
-- Set XML/XHTML/HTML "[%c\"&'<>]" Markup encodings: http://en.wikipedia.org/wiki/XML and http://en.wikipedia.org/wiki/HTML
local function setMarkupEncodings()
tblCodePage["\t"] = " " -- HT "\t" to "\r" are treated as white space in Markup Languages by default
tblCodePage["\n"] = br_Tag -- LF
tblCodePage["\v"] = br_Tag -- VT line break tag "
" or "
" or "
" or "
" is better
tblCodePage["\f"] = br_Tag -- FF
tblCodePage["\r"] = br_Tag -- CR
tblCodePage['"'] = """
tblCodePage["&"] = "&"
tblCodePage["'"] = "'"
tblCodePage["<"] = "<"
tblCodePage[">"] = ">"
end -- local function setMarkupEncodings
-- Set URI/URL/URN "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding
local function setURIEncodings()
tblCodePage["\t"] = "+" -- HT space
tblCodePage["\n"] = "%0A" -- LF newline
tblCodePage["\v"] = "%0A" -- VT newline
tblCodePage["\f"] = "%0A" -- FF newline
tblCodePage["\r"] = "%0D" -- CR return
tblCodePage['"'] = "%22"
tblCodePage["&"] = "%26"
tblCodePage["'"] = "%27"
tblCodePage["<"] = "%3C"
tblCodePage[">"] = "%3E"
end -- local function setURIEncodings
-- Encode characters according to gsub pattern & lookup table --
local function strEncode(strText,strPattern,tblPattern)
return ( (strText or ""):gsub(strPattern,tblPattern) ) -- V3.4
end -- local function strEncode
-- Encode CP1252/ANSI characters into UTF-8 codes --
function fh.StrANSI_UTF8(strText)
if fhVersion > 5 then
strText = fhConvertANSItoUTF8(strText)
else
strText = strEncode(strText,"[\127-ÿ]",tblCodePage)
end
return strText
end -- function StrANSI_UTF8
function fh.StrCP_UTF(strText) -- Legacy
return fh.StrANSI_UTF8(strText)
end -- function StrCP1252_UTF8
function fh.StrCP1252_UTF(strText) -- Legacy
return fh.StrANSI_UTF8(strText)
end -- function StrCP1252_UTF
-- Encode CP1252/ANSI or UTF-8 characters into UTF-8 --
function fh.StrEncode_UTF8(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_UTF8(strText)
else
return strText
end
end -- function StrEncode_UTF8
-- Encode CP1252/ANSI characters into XML/XHTML/HTML/UTF8 codes --
local strANSI_XML = "[%z\001-\031\"&'<>\127-ÿ]"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strANSI_XML = "[\000-\031\"&'<>\127-ÿ]"
end
function fh.StrANSI_XML(strText)
setMarkupEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag
strText = strEncode(strText,strANSI_XML,tblCodePage)
return strText
end -- function StrANSI_XML
function StrCP_XML(strText) -- Legacy
return fh.StrANSI_XML(strText)
end -- function StrCP_XML
function StrCP1252_XML(strText) -- Legacy
return fh.StrANSI_XML(strText)
end -- function StrCP1252_XML
-- Encode UTF-8 ASCII characters into XML/XHTML/HTML codes --
local strUTF8_XML = "[%z\001-\031\"&'<>\127]"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strUTF8_XML = "[\000-\031\"&'<>\127]"
end
function fh.StrUTF8_XML(strText)
setMarkupEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag
strText = strEncode(strText,strUTF8_XML,tblCodePage)
return strText
end -- function StrUTF8_XML
-- Encode CP1252/ANSI or UTF-8 ASCII characters into XML/XHTML/HTML codes --
function fh.StrEncode_XML(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_XML(strText)
else
return fh.StrUTF8_XML(strText)
end
end -- function StrEncode_XML
-- Encode Item Text characters into XML/HTML/UTF-8 codes --
function fh.StrGetItem_XML(ptrItem,strTags)
return fh.StrEncode_XML(fhGetItemText(ptrItem,strTags))
end -- function StrGetItem_XML
-- Encode CP1252/ANSI characters into URI codes --
function fh.StrANSI_URI(strText)
setURIEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes %0A
strText = strEncode(strText,"[^0-9A-Za-z]",tblCodePage)
return strText
end -- function StrANSI_URI
function fh.StrCP_URI(strText)
return fh.StrANSI_URI(strText)
end -- function StrCP_URI
function fh.StrCP1252_URI(strText)
return fh.StrANSI_URI(strText)
end -- function StrCP1252_URI
-- Encode UTF-8 ASCII characters into URI codes --
local strUTF8_URI = "[%z\001-\127]"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strUTF8_URI = "[\000-\127]"
end
function fh.StrUTF8_URI(strText)
setURIEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag
strText = strEncode(strText,strUTF8_URI,tblCodePage)
return strText
end -- function StrUTF8_URI
-- Encode CP1252/ANSI or UTF-8 ASCII characters into URI codes --
function fh.StrEncode_URI(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_URI(strText)
else
return fh.StrUTF8_URI(strText)
end
end -- function StrEncode_URI
function fh.StrUTF8_Encode(strText) -- Legacy from V1.0
return fh.StrUTF8_ANSI(strText)
end -- function StrUTF8_Encode
-- Encode UTF-8 bytes into single CP1252/ANSI character V2.0 upvalues --
local strByteRange = "["..string.char(0xC0).."-"..string.char(0xFF).."]"
local tblBytePoint = {0xC0;0xE0;0xF0;0xF8;0xFC;} -- Byte codes for 2-byte, 3-byte, 4-byte, 5-byte, 6-byte UTF-8
local tblUTF8 = {}
for strByte = string.byte("€"), string.byte("ÿ") do
local strChar = string.char(strByte) -- Use CodePage to UTF-8 table to populate UTF-8 to CodePage table
local strCode = tblCodePage[strChar]
tblUTF8[strCode] = strChar
end
-- Encode UTF-8 bytes into single CP1252/ANSI character --
function fh.StrUTF8_ANSI(strText)
strText = strText or ""
if fhVersion > 5 then return fhConvertUTF8toANSI(strText) end
if strText:match(strByteRange) then -- If text contains characters that need translating then
local intChar = 0 -- Input character index
local strChar = "" -- Current character
local strCode = "" -- UTF-8 multi-byte code
local tblLine = {} -- Translated output line
repeat
intChar = intChar + 1 -- Step through each character in text
strChar = strText:sub(intChar,intChar)
if strChar:match(strByteRange) then -- Convert UTF-8 bytes into CP character
strCode = strChar -- First UTF-8 byte code, whose top bits say how many bytes to append
for intByte, strByte in ipairs(tblBytePoint) do
if string.byte(strChar) >= strByte then
intChar = intChar + 1 -- Append next UTF-8 byte code character
strCode = strCode..strText:sub(intChar,intChar)
else
break
end
end
strChar = tblUTF8[strCode] or "¿" -- Translate UTF-8 code into CP character
end
table.insert(tblLine,strChar) -- Accumulate output char by char
until intChar >= #strText
strText = table.concat(tblLine)
end
return strText
end -- function StrUTF8_ANSI
function fh.StrUTF_CP(strText) -- Legacy
return fh.StrUTF8_ANSI(strText)
end -- function StrUTF_CP
function fh.StrUTF_CP1252(strText) -- Legacy
return fh.StrUTF8_ANSI(strText)
end -- function StrUTF_CP1252
-- Encode CP1252/ANSI or UTF-8 characters into ANSI --
function fh.StrEncode_ANSI(strText)
if stringx.encoding() == "ANSI" then
return strText or ""
else
return fh.StrUTF8_ANSI(strText)
end
end -- function StrEncode_ANSI
-- Set ISO-8859-1 "[\127-Ÿ]" encodings: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
local tblISO8859 = { }
tblISO8859["\127"]="" -- DEL
tblISO8859["€"] = "EUR"
tblISO8859["\129"]="" -- Undefined
tblISO8859["‚"] = "¸"
tblISO8859["ƒ"] = "f"
tblISO8859["„"] = "¸¸"
tblISO8859["…"] = "..."
tblISO8859["†"] = "+"
tblISO8859["‡"] = "±"
tblISO8859["ˆ"] = "^"
tblISO8859["‰"] = "%"
tblISO8859["Š"] = "S"
tblISO8859["‹"] = "<"
tblISO8859["Œ"] = "OE"
tblISO8859["\141"]="" -- Undefined
tblISO8859["Ž"] = "Z"
tblISO8859["\143"]="" -- Undefined
tblISO8859["\144"]="" -- Undefined
tblISO8859["‘"] = "'"
tblISO8859["’"] = "'"
tblISO8859["“"] = '"'
tblISO8859["”"] = '"'
tblISO8859["•"] = "º"
tblISO8859["–"] = "-"
tblISO8859["—"] = "-"
tblISO8859["\152"]="~" -- Small Tilde
tblISO8859["™"] = "TM"
tblISO8859["š"] = "s"
tblISO8859["›"] = ">"
tblISO8859["œ"] = "oe"
tblISO8859["\157"]="" -- Undefined
tblISO8859["ž"] = "z"
tblISO8859["Ÿ"] = "Y"
-- Encode CP1252/ANSI characters into ISO-8859-1 codes --
function fh.StrANSI_ISO(strText)
return strEncode(strText,"[\127-Ÿ]",tblISO8859)
end -- function StrANSI_ISO
function fh.StrCP_ISO(strText) -- Legacy
return fh.StrANSI_ISO(strText)
end -- function StrCP_ISO
function fh.StrCP1252_ISO(strText) -- Legacy
return fh.StrANSI_ISO(strText)
end -- function StrCP1252_ISO
function fh.StrUTF8_ISO(strText)
return fh.StrANSI_ISO(fh.StrUTF8_ANSI(strText))
end -- function StrUTF8_ISO
-- Encode CP1252/ANSI or UTF-8 ASCII characters into ISO-8859-1 codes --
function fh.StrEncode_ISO(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_ISO(strText)
else
return fh.StrUTF8_ISO(strText)
end
end -- function StrEncode_ISO
-- Convert UTF-8 bytes to a UTF-16 word or pair --
local tblByte = {}
local tblLead = { 0x80; 0xC0; 0xE0; 0xF0; 0xF8; 0xFC; }
function fh.StrUtf8toUtf16(strChar)
-- Convert any UTF-8 multibytes to UTF-16 --
local function strUtf8()
if #tblByte > 0 then
local intUtf16 = 0
for intIndex, intByte in ipairs (tblByte) do -- Convert UTF-8 bytes to UNICODE U+0080 to U+10FFFF
if intIndex == 1 then
intUtf16 = intByte - tblLead[#tblByte]
else
intUtf16 = intUtf16 * 0x40 + intByte - 0x80
end
end
if intUtf16 > 0xFFFF then -- U+10000 to U+10FFFF Supplementary Planes -- V2.6
tblByte = {}
intUtf16 = intUtf16 - 0x10000
local intLow10 = 0xDC00 + ( intUtf16 % 0x400 ) -- Low 16-bit Surrogate
local intTop10 = 0xD800 + math.floor( intUtf16 / 0x400 ) -- High 16-bit Surrogate
local intChar1 = intTop10 % 0x100
local intChar2 = math.floor( intTop10 / 0x100 )
local intChar3 = intLow10 % 0x100
local intChar4 = math.floor( intLow10 / 0x100 )
return string.char(intChar1,intChar2,intChar3,intChar4) -- Surrogate 16-bit Pair
end
if intUtf16 < 0xD800 -- U+0080 to U+FFFF (except U+D800 to U+DFFF) -- V2.6
or intUtf16 > 0xDFFF then -- Basic Multilingual Plane
tblByte = {}
local intChar1 = intUtf16 % 0x100
local intChar2 = math.floor( intUtf16 / 0x100 )
return string.char(intChar1,intChar2) -- BPL 16-bit
end
local strUtf8 = "" -- U+D800 to U+DFFF Reserved Code Points -- V2.6
for intIndex, intByte in ipairs (tblByte) do
strUtf8 = strUtf8..string.format("%.2X ",intByte)
end
local strUtf16 = string.format("%.4X ",intUtf16)
fhMessageBox("\n UTF-16 Reserved Code Point U+D800 to U+DFFF \n UTF-16 = "..strUtf16.." UTF-8 = "..strUtf8.."\n Character will be replaced by a question mark. \n","MB_OK","MB_ICONEXCLAMATION")
tblByte = {}
return "?\0"
end
return ""
end -- local function strUtf8
local intUtf8 = string.byte(strChar)
if intUtf8 < 0x80 then -- U+0000 to U+007F (ASCII)
return strUtf8()..strChar.."\0" -- Previous UTF-8 multibytes + current ASCII char
end
if intUtf8 >= 0xC0 then -- Next UTF-8 multibyte start
local strUtf16 = strUtf8()
table.insert(tblByte,intUtf8)
return strUtf16 -- Previous UTF-8 multibytes
end
table.insert(tblByte,intUtf8)
return ""
end -- function StrUtf8toUtf16
-- Encode UTF-8 bytes into UTF-16 words --
function fh.StrUTF8_UTF16(strText)
tblByte = {} -- (0xFF) flushes last UTF-8 character
return ( ((strText or "")..string.char(0xFF)):gsub("(.)",fh.StrUtf8toUtf16) ) -- V3.4
end -- function StrUTF8_UTF16
-- Encode CP1252/ANSI or UTF-8 characters into UTF-16 words --
function fh.StrEncode_UTF16(strText)
if stringx.encoding() == "ANSI" then
strText = fh.StrANSI_UTF8(strText)
end
return fh.StrUTF8_UTF16(strText)
end -- function StrEncode_UTF16
local intTop10 = 0
-- Convert a UTF-16 word or pair to UTF-8 bytes --
function fh.StrUtf16toUtf8(strChar1,strChar2)
local intUtf16 = string.byte(strChar2) * 0x100 + string.byte(strChar1)
if intUtf16 < 0x80 then -- U+0000 to U+007F (ASCII)
return string.char(intUtf16)
end
if intUtf16 < 0x800 then -- U+0080 to U+07FF
local intByte1 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte2 = intUtf16
return string.char( intByte2 + 0xC0, intByte1 + 0x80 )
end
if intUtf16 < 0xD800 -- U+0800 to U+FFFF
or intUtf16 > 0xDFFF then
local intByte1 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte2 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte3 = intUtf16
return string.char( intByte3 + 0xE0, intByte2 + 0x80, intByte1 + 0x80 )
end
if intUtf16 < 0xDC00 then -- U+10000 to U+10FFFF High 16-bit Surrogate Supplementary Planes -- V2.6
intTop10 = ( intUtf16 - 0xD800 ) * 0x400 + 0x10000
return ""
end
intUtf16 = intUtf16 - 0xDC00 + intTop10 -- U+10000 to U+10FFFF Low 16-bit Surrogate Supplementary Planes -- V2.6
local intByte1 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte2 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte3 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte4 = intUtf16
return string.char( intByte4 + 0xF0, intByte3 + 0x80, intByte2 + 0x80, intByte1 + 0x80 )
end -- function StrUtf16toUtf8
-- Encode UTF-16 words into UTF-8 bytes --
function fh.StrUTF16_UTF8(strText)
return ( (strText or ""):gsub("(.)(.)",fh.StrUtf16toUtf8) ) -- V3.4
end -- function StrUTF16_UTF8
-- Encode UTF-16 words into ANSI characters --
function fh.StrUTF16_ANSI(strText)
return fh.StrUTF8_ANSI(fh.StrUTF16_UTF8(strText))
end -- function StrUTF16_ANSI
-- Read UTF-16/UTF-8/ANSI file converted to chosen encoding via line iterator --
local strUtf16 = "^.%z"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strUtf16 = "^.\0"
end
function fh.FileLines(strFileName,strEncoding) -- Derived from http://lua-users.org/wiki/EnhancedFileLines
local bomUtf16= "^"..string.char(0xFF,0xFE) -- "ÿþ"
local bomUtf8 = "^"..string.char(0xEF,0xBB,0xBF) -- ""
local fncConv = tostring -- Function to convert input to current encoding
local intHead = 1 -- Index to start of current text line
local intLump = 1024
local fHandle = general.OpenFile(strFileName,"rb")
local strText = fHandle:read(1024) or "" -- Read first lump from file and cater for empty file
local intBOM = 0
strEncoding = strEncoding or string.encoding()
if strText:match(bomUtf16)
or strText:match(strUtf16) then
strText,intBOM = strText:gsub(bomUtf16,"") -- Strip UTF-16 BOM
if strEncoding == "ANSI" then -- Define UTF-16 conversion to current encoding
fncConv = fh.StrUTF16_ANSI
else
fncConv = fh.StrUTF16_UTF8
end
elseif strText:match(bomUtf8) then
strText,intBOM = strText:gsub(bomUtf8,"") -- Strip UTF-8 BOM
if strEncoding == "ANSI" then -- Define UTF-8 conversion to current encoding
fncConv = fh.StrUTF8_ANSI
end
else
if strEncoding == "UTF-8" and #strText > 0 then -- Define ANSI conversion to current encoding and cater for empty file
fncConv = fh.StrANSI_UTF8
end
end
strText = fncConv(strText) -- Convert first lump of text
return function() -- Iterator function
local intTail,strTail -- Index to end of current text line, and terminating characters
while true do
intTail, strTail = strText:match("()([\r\n].)",intHead)
if intTail or not fHandle then
if intHead > 1 then intLump = 0 end
break -- End of line or end of file
elseif fHandle then
local strLump = fHandle:read(1024) -- Read next lump from file
if strLump then -- Strip old text and add converted lump
strText = strText:sub(intHead)..fncConv(strLump)
intHead = 1
intLump = 1024
else
assert(fHandle:close()) -- End of file
fHandle = nil
end
end
end
if not intTail then
intTail = #strText -- Last fragment of file
elseif strTail == "\r\n" then
intTail = intTail + 1 -- Adjust tail for both \r & \n
end
local strLine = strText:sub(intHead,intTail) -- Extract line from text
intHead = intTail + 1
if #strLine > 0 then -- Return pruned line, tail chars, lump bytes read
local strBody, strTail = strLine:match("^(.-)([\r\n]+)$")
return strBody, strTail, intLump
end
end
end -- function FileLines
-- Set "[€-ÿ]" ASCII encodings same as Unidecode below
local tblASCII = { }
tblASCII["€"] = "=E"
tblASCII["\129"]="" -- Undefined
tblASCII["‚"] = ","
tblASCII["ƒ"] = "f"
tblASCII["„"] = ",,"
tblASCII["…"] = "..."
tblASCII["†"] = "|+"
tblASCII["‡"] = "|++"
tblASCII["ˆ"] = "^"
tblASCII["‰"] = "%0"
tblASCII["Š"] = "S"
tblASCII["‹"] = "<"
tblASCII["Œ"] = "OE"
tblASCII["\141"]="" -- Undefined
tblASCII["Ž"] = "Z"
tblASCII["\143"]="" -- Undefined
tblASCII["\144"]="" -- Undefined
tblASCII["‘"] = "'"
tblASCII["’"] = "'"
tblASCII["“"] = "\""
tblASCII["”"] = "\""
tblASCII["•"] = "*"
tblASCII["–"] = "-"
tblASCII["—"] = "--"
tblASCII["\152"]="~" -- Small Tilde
tblASCII["™"] = "TM"
tblASCII["š"] = "s"
tblASCII["›"] = ">"
tblASCII["œ"] = "oe"
tblASCII["\157"]="" -- Undefined
tblASCII["ž"] = "z"
tblASCII["Ÿ"] = "Y"
tblASCII["\160"]=" " -- " " No Break Space
tblASCII["¡"] = "!" -- "¡"
tblASCII["¢"] = "=c" -- "¢"
tblASCII["£"] = "=L" -- "£"
tblASCII["¤"] = "=$" -- "¤"
tblASCII["¥"] = "=Y" -- "¥"
tblASCII["¦"] = "|"
tblASCII["§"] = "=SS"
tblASCII["¨"] = "\""
tblASCII["©"] = "(C)"
tblASCII["ª"] = "a"
tblASCII["«"] = "<<"
tblASCII["¬"] = "-"
tblASCII[""] = "-" -- "" Soft Hyphen
tblASCII["®"] = "(R)"
tblASCII["¯"] = "-"
tblASCII["°"] = "=o"
tblASCII["±"] = "+-"
tblASCII["²"] = "2"
tblASCII["³"] = "3"
tblASCII["´"] = "'"
tblASCII["µ"] = "=u"
tblASCII["¶"] = "=p"
tblASCII["·"] = "*"
tblASCII["¸"] = ","
tblASCII["¹"] = "1"
tblASCII["º"] = "o"
tblASCII["»"] = ">>"
tblASCII["¼"] = "1/4"
tblASCII["½"] = "1/2"
tblASCII["¾"] = "3/4"
tblASCII["¿"] = "?"
tblASCII["À"] = "A"
tblASCII["Á"] = "A"
tblASCII["Â"] = "A"
tblASCII["Ã"] = "A"
tblASCII["Ä"] = "A"
tblASCII["Å"] = "A"
tblASCII["Æ"] = "AE"
tblASCII["Ç"] = "C"
tblASCII["È"] = "E"
tblASCII["É"] = "E"
tblASCII["Ê"] = "E"
tblASCII["Ë"] = "E"
tblASCII["Ì"] = "I"
tblASCII["Í"] = "I"
tblASCII["Î"] = "I"
tblASCII["Ï"] = "I"
tblASCII["Ð"] = "D"
tblASCII["Ñ"] = "N"
tblASCII["Ò"] = "O"
tblASCII["Ó"] = "O"
tblASCII["Ô"] = "O"
tblASCII["Õ"] = "O"
tblASCII["Ö"] = "O"
tblASCII["×"] = "*"
tblASCII["Ø"] = "O"
tblASCII["Ù"] = "U"
tblASCII["Ú"] = "U"
tblASCII["Û"] = "U"
tblASCII["Ü"] = "U"
tblASCII["Ý"] = "Y"
tblASCII["Þ"] = "TH"
tblASCII["ß"] = "ss"
tblASCII["à"] = "a"
tblASCII["á"] = "a"
tblASCII["â"] = "a"
tblASCII["ã"] = "a"
tblASCII["ä"] = "a"
tblASCII["å"] = "a"
tblASCII["æ"] = "ae"
tblASCII["ç"] = "c"
tblASCII["è"] = "e"
tblASCII["é"] = "e"
tblASCII["ê"] = "e"
tblASCII["ë"] = "e"
tblASCII["ì"] = "i"
tblASCII["í"] = "i"
tblASCII["î"] = "i"
tblASCII["ï"] = "i"
tblASCII["ð"] = "d"
tblASCII["ñ"] = "n"
tblASCII["ò"] = "o"
tblASCII["ó"] = "o"
tblASCII["ô"] = "o"
tblASCII["õ"] = "o"
tblASCII["ö"] = "o"
tblASCII["÷"] = "/"
tblASCII["ø"] = "o"
tblASCII["ù"] = "u"
tblASCII["ú"] = "u"
tblASCII["û"] = "u"
tblASCII["ü"] = "u"
tblASCII["ý"] = "y"
tblASCII["þ"] = "th"
tblASCII["ÿ"] = "y"
-- Encode CP1252/ANSI characters into ASCII codes [\000-\127] --
function fh.StrANSI_ASCII(strText)
return strEncode(strText,"[€-ÿ]",tblASCII)
end -- function StrANSI_ASCII
--[=[
Unidecode converts each codepoint into a few ASCII characters.
Lookup table indexed by codepoint [0x0000]-[0xFFFF] gives an ASCII string.
i.e. strASCII = Unidecode[intByte2][intByte1] or "=?" allowing for partially populated table.
See http://search.cpan.org/dist/Text-Unidecode/ and follow Browse to:
See http://cpansearch.perl.org/src/SBURKE/Text-Unidecode-1.22/lib/Text/Unidecode/
where each x??.pm gives 256 ASCII conversions.
Start with the first few European accented characters, and add the others later.
--]=]
local Unidecode = { }
function fh.StrUnidecode(strChar1,strChar2) -- Decode UTF-16 byte pair into ASCII characters
return Unidecode[string.byte(strChar2)][string.byte(strChar1)] or "=?"
end -- function StrUnidecode
-- Encode UTF-8 characters into ASCII codes [\000-\126] --
function fh.StrUTF8_ASCII(strText)
strText = fh.StrUTF8_UTF16(strText) -- Convert to UTF-16 Unicode and then to ASCII
return ( strText:gsub("(.)(.)",fh.StrUnidecode) )
end -- function StrUTF8_ASCII
-- Encode CP1252/ANSI or UTF-8 into ASCII codes [\000-\126] --
function fh.StrEncode_ASCII(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_ASCII(strText)
else
return fh.StrUTF8_ASCII(strText)
end
end -- function StrEncode_ASCII
-- Set markup language break tag --
function fh.SetBreakTag(br_New)
if not (br_New or ""):match(br_Lua) then -- Ensure new break tag is "
" or "
" or "
" or "
"
br_New = "
"
end
br_Tag = br_New
end -- function SetBreakTag
for intByte = 0x00, 0xFF do Unidecode[intByte] = { } end
Unidecode[0x00] =
{[0]="\00";"\01";"\02";"\03";"\04";"\05";"\06";"\a";"\b";"\t";"\n";"\v";"\f";"\r";"\14";"\15";"\16";"\17";"\18";"\19";"\20";"\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\30";"\31";
" ";"!";'"';"#";"$";"%";"&";"'";"(";")";"*";"+";",";"-";".";"/";"0";"1";"2";"3";"4";"5";"6";"7";"8";"9";":";";";"<";"=";">";"?"; -- 0x20 to 0x3F
"@";"A";"B";"C";"D";"E";"F";"G";"H";"I";"J";"K";"L";"M";"N";"O";"P";"Q";"R";"S";"T";"U";"V";"W";"X";"Y";"Z";"[";"\\";"]";"^";"_"; -- 0x40 to 0x5F
"`";"a";"b";"c";"d";"e";"f";"g";"h";"i";"j";"k";"l";"m";"n";"o";"p";"q";"r";"s";"t";"u";"v";"w";"x";"y";"z";"{";"|";"}";"~";"\127"; -- 0x60 to 0x7F
""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; -- 0x80 to 0x9F
" ";"!";"=c";"=L";"=$";"=Y";"|";"=SS";'"';"(C)";"a";"<<";"-";"-";"(R)";"-";"=o";"+-";"2";"3";"'";"=u";"=P";"*";",";"1";"o";">>";"1/4";"1/2";"3/4";"?"; -- 0xA0 to 0xBF
"A";"A";"A";"A";"A";"A";"AE";"C";"E";"E";"E";"E";"I";"I";"I";"I";"D";"N";"O";"O";"O";"O";"O";"*";"O";"U";"U";"U";"U";"Y";"TH";"ss"; -- 0xC0 to 0xDF
"a";"a";"a";"a";"a";"a";"ae";"c";"e";"e";"e";"e";"i";"i";"i";"i";"d";"n";"o";"o";"o";"o";"o";"/";"o";"u";"u";"u";"u";"y";"th";"y"; -- 0xE0 to 0xFF
}
Unidecode[0x01] =
{[0]="A";"a";"A";"a";"A";"a";"C";"c";"C";"c";"C";"c";"C";"c";"D";"d";"D";"d";"E";"e";"E";"e";"E";"e";"E";"e";"E";"e";"G";"g";"G";"g"; -- 0x00 to 0x1F
"G";"g";"G";"g";"H";"h";"H";"h";"I";"i";"I";"i";"I";"i";"I";"i";"I";"i";"IJ";"ij";"J";"j";"K";"k";"k";"L";"l";"L";"l";"L";"l";"L"; -- 0x20 to 0x3F
"l";"L";"l";"N";"n";"N";"n";"N";"n";"'n";"ng";"NG";"O";"o";"O";"o";"O";"o";"OE";"oe";"R";"r";"R";"r";"R";"r";"S";"s";"S";"s";"S";"s"; -- 0x40 to 0x5F
"S";"s";"T";"t";"T";"t";"T";"t";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"W";"w";"Y";"y";"Y";"Z";"z";"Z";"z";"Z";"z";"s"; -- 0x60 to 0x7F
"b";"B";"B";"b";"6";"6";"O";"C";"c";"D";"D";"D";"d";"d";"3";"@";"E";"F";"f";"G";"G";"hv";"I";"I";"K";"k";"l";"l";"W";"N";"n";"O"; -- 0x80 to 0x9F
"O";"o";"OI";"oi";"P";"p";"YR";"2";"2";"SH";"sh";"t";"T";"t";"T";"U";"u";"Y";"V";"Y";"y";"Z";"z";"ZH";"ZH";"zh";"zh";"2";"5";"5";"ts";"w"; -- 0xA0 to 0xBF
"|";"||";"|=";"!";"DZ";"Dz";"dz";"LJ";"Lj";"lj";"NJ";"Nj";"nj";"A";"a";"I";"i";"O";"o";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"@";"A";"a"; -- 0xC0 to 0xDF
"A";"a";"AE";"ae";"G";"g";"G";"g";"K";"k";"O";"o";"O";"o";"ZH";"zh";"j";"DZ";"Dz";"dz";"G";"g";"HV";"W";"N";"n";"A";"a";"AE";"ae";"O";"o"; -- 0xE0 to 0xFF
}
Unidecode[0x02] =
{[0]="A";"a";"A";"a";"E";"e";"E";"e";"I";"i";"I";"i";"O";"o";"O";"o";"R";"r";"R";"r";"U";"u";"U";"u";"S";"s";"T";"t";"Y";"y";"H";"h"; -- 0x00 to 0x1F
"N";"d";"OU";"ou";"Z";"z";"A";"a";"E";"e";"O";"o";"O";"o";"O";"o";"O";"o";"Y";"y";"l";"n";"t";"j";"db";"qp";"A";"C";"c";"L";"T";"s"; -- 0x20 to 0x3F
"z";"[?]";"[?]";"B";"U";"^";"E";"e";"J";"j";"q";"q";"R";"r";"Y";"y";"a";"a";"a";"b";"o";"c";"d";"d";"e";"@";"@";"e";"e";"e";"e";"j"; -- 0x40 to 0x5F
"g";"g";"g";"g";"u";"Y";"h";"h";"i";"i";"I";"l";"l";"l";"lZ";"W";"W";"m";"n";"n";"n";"o";"OE";"O";"F";"r";"r";"r";"r";"r";"r";"r"; -- 0x60 to 0x7F
"R";"R";"s";"S";"j";"S";"S";"t";"t";"u";"U";"v";"^";"w";"y";"Y";"z";"z";"Z";"Z";"?";"?";"?";"C";"@";"B";"E";"G";"H";"j";"k";"L"; -- 0x80 to 0x9F
"q";"?";"?";"dz";"dZ";"dz";"ts";"tS";"tC";"fN";"ls";"lz";"WW";"]]";"h";"h";"h";"h";"j";"r";"r";"r";"r";"w";"y";"'";'"';"`";"'";"`";"`";"'"; -- 0xA0 to 0xBF
"?";"?";"<";">";"^";"V";"^";"V";"'";"-";"/";"\\";",";"_";"\\";"/";":";".";"`";"'";"^";"V";"+";"-";"V";".";"@";",";"~";'"';"R";"X"; -- 0xC0 to 0xDF
"G";"l";"s";"x";"?";"";"";"";"";"";"";"";"V";"=";'"';"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF
}
Unidecode[0x03] =
{
}
Unidecode[0x04] =
{
}
Unidecode[0x20] =
{[0]=" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";"";"";"";"";"-";"-";"-";"-";"--";"--";"||";"_";"'";"'";",";"'";'"';'"';",,";'"'; -- 0x00 to 0x1F
"|+";"|++";"*";"*>";".";"..";"...";".";"\n";"\n\n";"";"";"";"";"";" ";"%0";"%00";"'";"''";"'''";"`";"``";"```";"^";"<";">";"*";"!!";"!?";"-";"_"; -- 0x20 to 0x3F
"-";"^";"***";"--";"/";"-[";"]-";"[?]";"?!";"!?";"7";"PP";"(]";"[)";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x40 to 0x5F
"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"0";"";"";"";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"n"; -- 0x60 to 0x7F
"0";"1";"2";"3";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x80 to 0x9F
"ECU";"CL";"Cr";"FF";"L";"mil";"N";"Pts";"Rs";"W";"NS";"D";"=E";"K";"T";"Dr";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xA0 to 0xBF
"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";""; -- 0xC0 to 0xDF
"";"";"";"";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF
}
Unidecode[0x21] =
{[34]="TM";
}
return fh
end -- local function encoder_v3
local encoder = encoder_v3() -- To access FH encoder chars module
--[[
@Module: +fh+progbar_v3
@Author: Mike Tate
@Version: 3.1
@LastUpdated: 23 Jan 2026
@Description: Progress Bar library module.
@V3.1: Use NATIVEPARENT amd CENTERPARENT.
@V3.0: Function Prototype Closure version.
@V1.0: Initial version.
]]
local function progbar_v3()
local fh = {} -- Local environment table
require "iuplua" -- To access GUI window builder
iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28
local tblBars = {} -- Table for optional external attributes
local strBack = "255 255 255" -- Background colour default is white
local strBody = "0 0 0" -- Body text colour default is black
local strFont = nil -- Font dialogue default is current font
local strStop = "255 0 0" -- Stop button colour default is red
local intPosX = iup.CENTERPARENT -- Show window default position is central -- V3.1
local intPosY = iup.CENTERPARENT
local intMax, intVal, intPercent, intStart, intDelta, intScale, strClock, isBarStop
local lblText, barGauge, lblDelta, btnStop, dlgGauge
local function doFocus() -- Bring the Progress Bar window into Focus
dlgGauge.BringFront="YES" -- If used too often, inhibits other windows scroll bars, etc
end -- local function doFocus
local function doUpdate() -- Update the Progress Gauge and the Delta % with clock
barGauge.Value = intVal
lblDelta.Title = string.format("%4d %% %s ",math.floor(intPercent),strClock)
end -- local function doUpdate
local function doReset() -- Reset all dialogue variables and Update display
intVal = 0 -- Current value of Progress Bar
intPercent= 0.01 -- Percentage of progress
intStart = os.time() -- Start time of progress
intDelta = 0 -- Delta time of progress
intScale = math.ceil( intMax / 1000 ) -- Scale of percentage per second of progress (initial guess is corrected in Step function)
strClock = "00 : 00 : 00" -- Clock delta time display
isBarStop = false -- Stop button pressed signal
doUpdate()
doFocus()
end -- local function doReset
function fh.Start(strTitle,intMaximum) -- Create & start Progress Bar window
if not dlgGauge then
strTitle = strTitle or "" -- Dialogue and button title
intMax = intMaximum or 100 -- Maximun range of Progress Bar, default is 100
local strSize = tostring( math.max( 100, string.len(" Stop "..strTitle) * 8 ) ).."x30" -- Adjust Stop button size to Title
lblText = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Progress Message"; }
barGauge = iup.progressbar { RasterSize="400x30"; Value=0; Max=intMax; Tip="Progress Bar"; }
lblDelta = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Percentage and Elapsed Time"; }
btnStop = iup.button { Title=" Stop "..strTitle; RasterSize=strSize; FgColor=strStop; Tip="Stop Progress Button"; action=function() isBarStop = true end; } -- Signal Stop button pressed return iup.CLOSE -- Often caused main GUI to close !!!
dlgGauge = iup.dialog { Title=strTitle.." Progress "; Font=strFont; FgColor=strBody; Background=strBack; DialogFrame="YES"; -- Remove Windows minimize/maximize menu
iup.vbox{ Alignment="ACENTER"; Gap="10"; Margin="10x10";
lblText;
barGauge;
lblDelta;
btnStop;
};
move_cb = function(self,x,y) tblBars.X = x tblBars.Y = y end;
close_cb = btnStop.action; -- Windows Close button = Stop button
}
if type(tblBars.GUI) == "table"
and type(tblBars.GUI.ShowDialogue) == "function" then
dlgGauge.move_cb = nil -- Use GUI library to show & move window
tblBars.GUI.ShowDialogue("Bars",dlgGauge,btnStop,"showxy")
else
if fhGetAppVersion() > 6 then -- Window centres on FH parent -- V3.1
iup.SetAttribute(dlgGauge,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
dlgGauge:showxy(intPosX,intPosY) -- Show the Progress Bar window
end
doReset() -- Reset the Progress Bar display
end
end -- function Start
function fh.Message(strText) -- Show the Progress Bar message
if dlgGauge then lblText.Title = strText end
end -- function Message
function fh.Step(intStep) -- Step the Progress Bar forward
if dlgGauge then
intVal = intVal + ( intStep or 1 ) -- Default step is 1
local intNew = math.ceil( intVal / intMax * 100 * intScale ) / intScale
if intPercent ~= intNew then -- Update progress once per percent or per second, whichever is smaller
intPercent = math.max( 0.1, intNew ) -- Ensure percentage is greater than zero
if intVal > intMax then intVal = intMax intPercent = 100 end -- Ensure values do not exceed maximum
intNew = os.difftime(os.time(),intStart)
if intDelta < intNew then -- Update clock of elapsed time
intDelta = intNew
intScale = math.ceil( intDelta / intPercent ) -- Scale of seconds per percentage step
local intHour = math.floor( intDelta / 3600 )
local intMins = math.floor( intDelta / 60 - intHour * 60 )
local intSecs = intDelta - intMins * 60 - intHour * 3600
strClock = string.format("%02d : %02d : %02d",intHour,intMins,intSecs)
end
doUpdate() -- Update the Progress Bar display
end
iup.LoopStep()
end
end -- function Step
function fh.Focus() -- Bring the Progress Bar window to front
if dlgGauge then doFocus() end
end -- function Focus
function fh.Reset() -- Reset the Progress Bar display
if dlgGauge then doReset() end
end -- function Reset
function fh.Stop() -- Check if Stop button pressed
iup.LoopStep()
return isBarStop
end -- function Stop
function fh.Close() -- Close the Progress Bar window
isBarStop = false
if dlgGauge then dlgGauge:destroy() dlgGauge = nil end
end -- function Close
function fh.Setup(tblSetup) -- Setup optional table of external attributes
if tblSetup then
tblBars = tblSetup
strBack = tblBars.Back or strBack -- Background colour
strBody = tblBars.Body or strBody -- Body text colour
strFont = tblBars.Font or strFont -- Font dialogue
strStop = tblBars.Stop or strStop -- Stop button colour
intPosX = tblBars.X or intPosX -- Window position
intPosY = tblBars.Y or intPosY
end
end -- function Setup
return fh
end -- local function progbar_v3
local progbar = progbar_v3() -- To access FH progress bars module
--[[
@Module: +fh+iup_gui_v3
@Author: Mike Tate
@Version: 4.4
@LastUpdated: 29 Jan 2026
@Description: Graphical User Interface Library Module
@V4.4: Introduce use of NATIVEPARENT and CENTERPARENT to centre on parent window by default; Ensure not off screen; Monitors with -ve X;
@V4.3: Added memo options to CheckVersionInStore;
@V4.2: Skip if standalone GEDCOM in fh.SaveSettings() and getDataFiles();
@V4.1: CheckVersionInStore() save & retrieve latest version in file; Remove old wiki Help features;
@V4.0: Cater for full UTF-8 filenames;
@V3.9: ShowDialogue() popup closure fhSleep() added; CheckVersionInStore() at monthly intervals;
@V3.8: Function Prototype Closure version.
@V3.7: AssignAttributes(tblControls) now allows any string attribute to invoke a function.
@V3.6: anyMemoDialogue() sets TopMost attribute.
@V3.5: Replace IsNormalWindow(iupDialog) with SetWindowCoord(tblName) and update CheckWindowPosition(tblName) to prevent negative values freezing main dialog.
@V3.4: Use general.MakeFolder() to ensure key folders exist, add Get/PutRegKey(), check Registry IE Shell Version in HelpDialogue(), better error handling in LoadSettings().
@V3.3: LoadFolder() and SaveFolder() use global folder as default for local folder to improve synch across PC.
@V3.2: Load & Save settings now use a single clipboard so Local PC settings are preserved across synchronised PC.
@V3.1: IUP 3.11.2 iup.GetGlobal("VERSION") to top, HelpDialogue conditional ExpandChildren="YES/NO", RefreshDialogue uses NaturalSize, SetUtf8Mode(), Load/SaveFolder(), etc
@V3.0: ShowDialogue "dialog" mode for Memo, new DestroyDialogue, NewHelpDialogue tblAttr for Font, AssignAttributes intSkip, CustomDialogue iup.CENTERPARENT+, IUP Workaround, BalloonToggle, Initialise test Plugin file exists.
@V2.0: Support for Plugin Data scope, new FontDialogue, RefreshDialogue, AssignAttributes, httpRequest handler, keep "dialog" mode.
@V1.0: Initial version.
]]
local function iup_gui_v3()
local fh = {} -- Local environment table
require "iuplua" -- To access GUI window builder
require "iupluacontrols" -- To access GUI window controls
require "lfs" -- To access LUA filing system
require "iupluaole" -- To access OLE subsystem
require "luacom" -- To access COM subsystem
iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28
local iupVersion = iup.GetGlobal("VERSION") -- Obtain IUP module version
-- "iuplua" Omitted Constants Workaround --
iup.TOP = iup.LEFT
iup.BOTTOM = iup.RIGHT
iup.RED = iup.RGB(1,0,0)
iup.GREEN = iup.RGB(0,1,0)
iup.BLUE = iup.RGB(0,0,1)
iup.BLACK = iup.RGB(0,0,0)
iup.WHITE = iup.RGB(1,1,1)
iup.YELLOW = iup.RGB(1,1,0)
-- Shared Interface Attributes & Functions --
fh.Version = " " -- Plugin Version
fh.History = fh.Version -- Version History
fh.Red = "255 0 0" -- Color attributes (must exclude leading zeros & spaces to allow value comparisons)
fh.Maroon = "128 0 0"
fh.Amber = "250 160 0"
fh.Orange = "255 165 0"
fh.Yellow = "255 255 0"
fh.Olive = "128 128 0"
fh.Lime = "0 255 0"
fh.Green = "0 128 0"
fh.Cyan = "0 255 255"
fh.Teal = "0 128 128"
fh.Blue = "0 0 255"
fh.Navy = "0 0 128"
fh.Magenta = "255 0 255"
fh.Purple = "128 0 128"
fh.Black = "0 0 0"
fh.Gray = "128 128 128"
fh.Silver = "192 192 192"
fh.Smoke = "240 240 240"
fh.White = "255 255 255"
fh.Risk = fh.Red -- Risk colour for hazardous controls such as Close/Delete buttons
fh.Warn = fh.Magenta -- Warn colour for caution controls and warnings
fh.Safe = fh.Green -- Safe colour for active controls such as most buttons
fh.Info = fh.Black -- Info colour for text controls such as labels/tabs
fh.Head = fh.Black -- Head colour for headings
fh.Body = fh.Black -- Body colour for body text
fh.Back = fh.White -- Background colour for all windows
fh.Gap = "8" -- Layout attributes Gap was "10"
fh.Border = "8x8" -- was BigMargin="10x10"
fh.Margin = "1x1" -- was MinMargin
fh.Balloon = "NO" -- Tooltip balloon mode
fh.FontSet = 0 -- Legacy GUI font set assigned by FontAssignment but used globally
fh.FontHead = ""
fh.FontBody = ""
local GUI = { } -- Sub-table for GUI Dialogue attributes to allow any "Name"
--[[
GUI.Name table of dialogue attributes, where Name is Font, Main, Memo, Bars, etc
GUI.Name.CoordX x co-ordinate ( Loaded & Saved by default )
GUI.Name.CoordY y co-ordinate ( Loaded & Saved by default )
GUI.Name.Dialog dialogue handle
GUI.Name.Focus focus button handle
GUI.Name.Frame dialogframe mode, "normal" = dialogframe="NO" else "YES", "showxy" = showxy(), "popup" or "keep" = popup(), default is "normal & showxy"
GUI.Name.Height height
GUI.Name.Raster rastersize ( Loaded & Saved by default )
GUI.Name.Width width
GUI.Name.Back ProgressBar background colour
GUI.Name.Body ProgressBar body text colour
GUI.Name.Font ProgressBar font style
GUI.Name.Stop ProgressBar Stop button colour
GUI.Name.GUI Module table usable by other modules e.g. progbar.Setup
--]]
-- tblScrn[1] = origin x, tblScrn[2] = origin y, tblScrn[3] = width, tblScrn[4] = height
local tblScrn = stringx.splitnumbers(iup.GetGlobal("VIRTUALSCREEN")) -- Used by CustomDialogue() and CheckWindowPosition() and ShowDialogue() below
local intMinX = tblScrn[1]
local intMinY = tblScrn[2] -- V4.4
local intMaxW = tblScrn[3]
local intMaxH = tblScrn[4]
function fh.BalloonToggle() -- Toggle tooltips Balloon mode
local tblToggle = { YES="NO"; NO="YES"; }
fh.Balloon = tblToggle[fh.Balloon]
fh.SaveSettings()
end -- function BalloonToggle
iup.SetGlobal("UTF8MODE","NO")
iup.SetGlobal("UTF8MODE_FILE","NO") -- V4.0
function fh.SetUtf8Mode() -- Set IUP into UTF-8 mode
if iupVersion == "3.5" or stringx.encoding() == "ANSI" then return false end
iup.SetGlobal("UTF8MODE","YES")
iup.SetGlobal("UTF8MODE_FILE","YES") -- V4.0
return true
end -- function SetUtf8Mode
local function tblOfNames(...) -- Get table of dialogue Names including "Font","Main" by default -- V4.4
local arg = {...}
local tblNames = {"Font";"Main";}
for intName, strName in ipairs(arg) do
if type(strName) == "string"
and strName ~= "Font"
and strName ~= "Main" then
table.insert(tblNames,strName)
end
end
return tblNames
end -- local function tblOfNames
local function tblNameFor(strName) -- Get table of parameters for chosen dialogue Name
strName = tostring(strName)
if not GUI[strName] then -- Need new table with default minimum & raster size, and X & Y co-ordinates
GUI[strName] = { }
local tblName = GUI[strName]
tblName.Raster = "x"
tblName.CoordX = iup.CENTERPARENT
tblName.CoordY = iup.CENTERPARENT
end
return GUI[strName]
end -- local function tblNameFor
local function intDimension(intMin,intVal,intMax) -- Return a number bounded by intMin and intMax
if not intVal then return 0 end -- Except if no value then return 0
intVal = tonumber(intVal) or (intMin+intMax)/2
return math.max(intMin,math.min(intVal,intMax))
end -- local function intDimension
function fh.CustomDialogue(strName,strRas,intX,intY) -- GUI custom window raster size, and X & Y co-ordinates
-- strRas nil = old size, "x" or "0x0" = min size, "999x999" = new size
-- intX/Y nil = central, "99" = co-ordinate position
local tblName = tblNameFor(strName)
local tblSize = {}
local intWide = 0
local intHigh = 0
strRas = strRas or tblName.Raster
if strRas then -- Ensure raster size is between minimum and screen size
tblSize = stringx.splitnumbers(strRas)
intWide = intDimension(intWide,tblSize[1],intMaxW)
intHigh = intDimension(intHigh,tblSize[2],intMaxH)
strRas = tostring(intWide.."x"..intHigh)
end
if intX and intX < iup.CENTERPARENT then
intX = intDimension(intMinX,intX,intMaxW-intWide) -- Ensure X co-ordinate positions window on screen -- V4.4
end
if intY and intY < iup.CENTERPARENT then
intY = intDimension(intMinY,intY,intMaxH-intHigh) -- Ensure Y co-ordinate positions window on screen -- V4.4
end
tblName.Raster = strRas or "x"
tblName.CoordX = tonumber(intX) or iup.CENTERPARENT -- V4.4
tblName.CoordY = tonumber(intY) or iup.CENTERPARENT -- V4.4
end -- function CustomDialogue
function fh.DefaultDialogue(...) -- GUI default window minimum & raster size, and X & Y co-ordinates
for intName, strName in ipairs(tblOfNames(...)) do
fh.CustomDialogue(strName)
end
end -- function DefaultDialogue
function fh.DialogueAttributes(strName) -- Provide named Dialogue Attributes
local tblName = tblNameFor(strName) -- tblName.Dialog = dialog handle, so any other attributes could be retrieved
local tblSize = stringx.splitnumbers(tblName.Raster or "x") -- Split Raster Size into width=tblSize[1] and height=tblSize[2]
tblName.Width = tblSize[1]
tblName.Height= tblSize[2]
tblName.Back = fh.Back -- Following only needed for NewProgressBar
tblName.Body = fh.Body
tblName.Font = fh.FontBody
tblName.Stop = fh.Risk
tblName.GUI = fh -- Module table
return tblName
end -- function DialogueAttributes
local strDefaultScope = "Project" -- Default scope for Load/Save data is per Project/User/Machine as set by PluginDataScope()
local tblClipProj = { }
local tblClipUser = { } -- Clipboards of sticky data for each Plugin Data scope -- V3.2
local tblClipMach = { }
local function doLoadData(strParam,strDefault,strScope) -- Load sticky data for Plugin Data scope
strScope = tostring(strScope or strDefaultScope):lower()
local tblClipData = tblClipProj
if strScope:match("user") then tblClipData = tblClipUser
elseif strScope:match("mach") then tblClipData = tblClipMach
end
return tblClipData[strParam] or strDefault
end -- local function doLoadData
function fh.LoadGlobal(strParam,strDefault,strScope) -- Load Global Parameter for all PC
return doLoadData(strParam,strDefault,strScope)
end -- function LoadGlobal
function fh.LoadLocal(strParam,strDefault,strScope) -- Load Local Parameter for this PC
return doLoadData(fh.ComputerName.."-"..strParam,strDefault,strScope)
end -- function LoadLocal
local function doLoadFolder(strFolder) -- Use relative paths to let Paths change -- V3.3
strFolder = strFolder:gsub("^FhDataPath",function() return fh.FhDataPath end) -- Full path to .fh_data folder
strFolder = strFolder:gsub("^PublicPath",function() return fh.PublicPath end) -- Full path to Public folder
strFolder = strFolder:gsub("^FhProjPath",function() return fh.FhProjPath end) -- Full path to project folder
return strFolder
end -- local function doLoadFolder
function fh.LoadFolder(strParam,strDefault,strScope) -- Load Folder Parameter for this PC -- V3.3
local strFolder = doLoadFolder(fh.LoadLocal(strParam,"",strScope))
if not general.FlgFolderExists(strFolder) then -- If no local folder try global folder
strFolder = doLoadFolder(fh.LoadGlobal(strParam,strDefault,strScope))
end
return strFolder
end -- function LoadFolder
function fh.LoadDialogue(...) -- Load Dialogue Parameters for "Font","Main" by default
for intName, strName in ipairs(tblOfNames(...)) do
local tblName = tblNameFor(strName)
tblName.Raster = tostring(fh.LoadLocal(strName.."R",tblName.Raster))
tblName.CoordX = tonumber(fh.LoadLocal(strName.."X",tblName.CoordX))
tblName.CoordY = tonumber(fh.LoadLocal(strName.."Y",tblName.CoordY))
fh.CheckWindowPosition(tblName)
end
end -- function LoadDialogue
function fh.LoadSettings(...) -- Load Sticky Settings from File
for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do
strFileName = fh[strFileName]
if general.FlgFileExists(strFileName) then -- Load Settings File in table lines with key & val fields
local tblField = {}
local strClip = general.StrLoadFromFile(strFileName) -- V4.0
for strLine in strClip:gmatch("[^\r\n]+") do -- V4.0
if #tblField == 0
and strLine:match("^return {") -- Unless entire Sticky Data table was saved
and type(table.load) == "function" then
local tblClip, strErr = table.load(strFileName) -- Load Settings File table
if strErr then error(strErr.."\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.") end
for i,j in pairs (tblClip) do
tblClipData[i] = tblClip[i]
end
break
end
tblField = stringx.split(strLine,"=")
if tblField[1] then tblClipData[tblField[1]] = tblField[2] end
end
else
for i,j in pairs (tblClipData) do
tblClipData[i] = nil -- Restore defaults and clear any junk -- V4.0
end
end
end
fh.Safe = tostring(fh.LoadGlobal("SafeColor",fh.Safe))
fh.Warn = tostring(fh.LoadGlobal("WarnColor",fh.Warn))
fh.Risk = tostring(fh.LoadGlobal("RiskColor",fh.Risk))
fh.Head = tostring(fh.LoadGlobal("HeadColor",fh.Head))
fh.Body = tostring(fh.LoadGlobal("BodyColor",fh.Body))
fh.FontHead= tostring(fh.LoadGlobal("FontHead" ,fh.FontHead))
fh.FontBody= tostring(fh.LoadGlobal("FontBody" ,fh.FontBody))
fh.History = tostring(fh.LoadGlobal("History" ,fh.History))
fh.Balloon = tostring(fh.LoadGlobal("Balloon" ,fh.Balloon, "Machine"))
fh.LoadDialogue(...)
end -- function LoadSettings
local function doSaveData(strParam,anyValue,strScope) -- Save sticky data for Plugin Data scope
strScope = tostring(strScope or strDefaultScope):lower()
local tblClipData = tblClipProj
if strScope:match("user") then tblClipData = tblClipUser
elseif strScope:match("mach") then tblClipData = tblClipMach
end
tblClipData[strParam] = anyValue
end -- local function doSaveData
function fh.SaveGlobal(strParam,anyValue,strScope) -- Save Global Parameter for all PC
doSaveData(strParam,anyValue,strScope)
end -- function SaveGlobal
function fh.SaveLocal(strParam,anyValue,strScope) -- Save Local Parameter for this PC
doSaveData(fh.ComputerName.."-"..strParam,anyValue,strScope)
end -- function SaveLocal
function fh.SaveFolder(strParam,strFolder,strScope) -- Save Folder Parameter for this PC
strFolder = stringx.replace(strFolder,fh.FhDataPath,"FhDataPath") -- Full path to .fh_data folder
strFolder = stringx.replace(strFolder,fh.PublicPath,"PublicPath") -- Full path to Public folder
strFolder = stringx.replace(strFolder,fh.FhProjPath,"FhProjPath") -- Full path to project folder
fh.SaveGlobal(strParam,strFolder,strScope) -- V3.3
fh.SaveLocal(strParam,strFolder,strScope) -- Uses relative paths to let Paths change
end -- function SaveFolder
function fh.SaveDialogue(...) -- Save Dialogue Parameters for "Font","Main" by default
for intName, strName in ipairs(tblOfNames(...)) do
local tblName = tblNameFor(strName)
fh.SaveLocal(strName.."R",tblName.Raster)
fh.SaveLocal(strName.."X",tblName.CoordX)
fh.SaveLocal(strName.."Y",tblName.CoordY)
end
end -- function SaveDialogue
function fh.SaveSettings(...) -- Save Sticky Settings to File
fh.SaveDialogue(...)
fh.SaveGlobal("SafeColor",fh.Safe)
fh.SaveGlobal("WarnColor",fh.Warn)
fh.SaveGlobal("RiskColor",fh.Risk)
fh.SaveGlobal("HeadColor",fh.Head)
fh.SaveGlobal("BodyColor",fh.Body)
fh.SaveGlobal("FontHead" ,fh.FontHead)
fh.SaveGlobal("FontBody" ,fh.FontBody)
fh.SaveGlobal("History" ,fh.History)
fh.SaveGlobal("Balloon" ,fh.Balloon, "Machine")
for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do
for i,j in pairs (tblClipData) do -- Check if table has any entries
strFileName = fh[strFileName]
if #strFileName > 0 then -- Skip for standalone GEDCOM -- V4.2
if type(table.save) == "function" then -- Save entire Settings File table per Project/User/Machine
table.save(tblClipData,strFileName)
else
local tblClip = {}
for strKey,strVal in pairs(tblClipData) do -- Else save Settings File lines with key & val fields -- V4.0
table.insert(tblClip,strKey.."="..strVal.."\n") -- V4.0
end
local strClip = table.concat(tblClip,"\n") -- V4.0
if not general.SaveStringToFile(strClip,strFileName) then
error("\nSettings file not saved successfully.\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.")
end
end
end
break
end
end
end -- function SaveSettings
function fh.CheckWindowPosition(tblName) -- Ensure dialogue window coordinates are on Screen
local tblSize = stringx.splitnumbers(tblName.Raster or "600x400","x") -- Get window dimensions from the previous use of plugin -- V4.4
local intWinW = tblSize[1]
local intWinH = tblSize[2]
if tonumber(tblName.CoordX) == nil
or tonumber(tblName.CoordY) == nil
or tonumber(tblName.CoordX) < intMinX -- V4.4 -- V3.5
or tonumber(tblName.CoordY) < intMinY -- V4.4 -- V3.5
or tonumber(tblName.CoordX) + intWinW > intMaxW -- V4.4
or tonumber(tblName.CoordY) + intWinH > intMaxH then -- V4.4
tblName.CoordX = iup.CENTERPARENT -- V4.4
tblName.CoordY = iup.CENTERPARENT -- V4.4
end
end -- function CheckWindowPosition
function fh.SetWindowCoord(tblName) -- Set the Window coordinates if not Maximised or Minimised -- V3.5
-- tblPosn[1] = origin x, tblPosn[2] = origin y, tblPosn[3] = width, tblPosn[4] = height
local tblPosn = stringx.splitnumbers(tblName.Dialog.ScreenPosition)
local intPosX = tblPosn[1] or -1
local intPosY = tblPosn[2] or -1
if intPosX < 0 and intPosY < 0 then -- If origin is negative (-8, -8 = Maximised, -3200, -3200 = Minimised)
return false -- then is Maximised or Minimised
end
tblName.CoordX = intPosX -- Otherwise set the Window coordinates
tblName.CoordY = intPosY
return true
end -- function SetWindowCoord
function fh.ShowDialogue(strName,iupDialog,btnFocus,strFrame) -- Set standard frame attributes and display dialogue window
local tblName = tblNameFor(strName)
iupDialog = iupDialog or tblName.Dialog -- Retrieve previous parameters if needed
btnFocus = btnFocus or tblName.Focus
strFrame = strFrame or tblName.Frame
strFrame = strFrame or "show norm" -- Default frame mode is dialog:showxy(X,Y) with DialogFrame="NO" ("normal" to vary size, otherwise fixed size)
strFrame = strFrame:lower() -- Other modes are "show", "popup" & "keep" with DialogFrame="YES", or with "normal" for DialogFrame="NO" ("show" for active windows, "popup"/"keep" for modal windows)
if strFrame:gsub("%s-%a-map%a*[%s%p]*","") == "" then -- May be prefixed with "map" mode to just map dialogue initially, also may be suffixed with "dialog" to inhibit iup.MainLoop() to allow progress messages
strFrame = "map show norm" -- If only "map" mode then default to "map show norm"
end
if type(iupDialog) == "userdata" then
tblName.Dialog = iupDialog
tblName.Focus = btnFocus -- Preserve parameters
tblName.Frame = strFrame
iupDialog.Background = fh.Back -- Background colour
iupDialog.Shrink = "YES" -- Sometimes needed to shrink controls to raster size
if type(btnFocus) == "userdata" then -- Set button as focus for Esc and Enter keys
iupDialog.StartFocus = iupDialog.StartFocus or btnFocus
iupDialog.DefaultEsc = iupDialog.DefaultEsc or btnFocus
iupDialog.DefaultEnter = iupDialog.DefaultEnter or btnFocus
end
if tblName.CoordX == iup.CENTERPARENT
or tblName.CoordY == iup.CENTERPARENT then -- When centred on parent minimise this window size -- V4.4
tblName.Raster= iupDialog.MinSize
else
iupDialog.MinSize = "x" -- Minimum size (default "x" becomes nil)
iupDialog.RasterSize = tblName.Raster or "x" -- Raster size (default "x" becomes nil)
end
iupDialog.MaxSize = intMaxW.."x"..intMaxH -- Maximum size is virtual screen size
if strFrame:match("norm") then -- DialogFrame mode is "NO" by default for variable size window
if strFrame:match("pop") or strFrame:match("keep") then
iupDialog.MinBox = "NO" -- For "popup" and "keep" hide Minimize and Maximize icons
iupDialog.MaxBox = "NO"
else
strFrame = strFrame.." show" -- If not "popup" nor "keep" then use "showxy" mode
end
else
iupDialog.DialogFrame = "YES" -- Define DialogFrame mode for fixed size window
end
iupDialog.close_cb = iupDialog.close_cb or function() return iup.CLOSE end -- Define default window X close, move, and resize actions
iupDialog.move_cb = iupDialog.move_cb or function(self) if iup.MainLoopLevel() > 0 then fh.SetWindowCoord(tblName) end end -- V3.5 -- V4.4
iupDialog.resize_cb = iupDialog.resize_cb or function(self) if iup.MainLoopLevel() > 0 and fh.SetWindowCoord(tblName) then tblName.Raster=self.RasterSize end end -- V3.5 -- V4.4
fh.RefreshDialogue(strName) -- Refresh to set Natural Size as Minimum Size
if strName == "Main" then
if fhGetAppVersion() > 6 then -- Main window centres on FH parent -- V4.4
iup.SetAttribute(iupDialog,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
else
local tblMain = tblNameFor("Main") -- Others popup centrally in Main -- V4.4
local iupMain = tblMain.Dialog
if iupMain then -- Centre based on size of windows -- V1.4
local arrName = stringx.splitnumbers(tblName.Raster or "0x0")
local arrMain = stringx.splitnumbers(tblMain.Raster or arrName[1].."x"..arrName[2])
tblName.CoordX = iupMain.X + math.floor( (arrMain[1] - arrName[1]) / 2 )
tblName.CoordY = iupMain.Y + math.floor( (arrMain[2] - arrName[2]) / 2 )
elseif fhGetAppVersion() > 6 then -- This window centres on FH parent -- V4.4
iup.SetAttribute(iupDialog,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
end
if strFrame:match("map") then -- Only dialogue mapping is required
iupDialog:map()
tblName.Frame = strFrame:gsub("%s-%a-map%a*[%s%p]*","") -- Remove "map" from frame mode ready for subsequent call
return
end
if iup.MainLoopLevel() == 0 -- Called from outside Main GUI, so must use showxy() and not popup()
or strFrame:match("dialog")
or strFrame:match("sho") then -- Use showxy() to dispay dialogue window for "showxy" or "dialog" mode
iupDialog:showxy(tblName.CoordX,tblName.CoordY)
if not strFrame:match("dialog") -- Inhibit MainLoop if "dialog" mode -- V4.1
and iup.MainLoopLevel() == 0 then iup.MainLoop() end
else
iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to display dialogue window for "popup" or "keep" modes
fhSleep(200,150) -- Sometimes needed to prevent MainLoop() closure! -- V3.9
end
if not strFrame:match("dialog") and strFrame:match("pop") then
tblName.Dialog = nil -- When popup closed, clear key parameters, but not for "keep" mode
tblName.Raster = nil
tblName.CoordX = nil -- iup.CENTERPARENT
tblName.CoordY = nil -- iup.CENTERPARENT
elseif tblName.CoordX ~= iup.CENTERPARENT -- V4.4
and tblName.CoordY ~= iup.CENTERPARENT then
fh.SetWindowCoord(tblName) -- Set Window coordinate pixel values -- V3.5
end
end
end -- function ShowDialogue
function fh.DestroyDialogue(strName) -- Destroy existing dialogue
local tblName = tblNameFor(strName)
if tblName then
local iupDialog = tblName.Dialog
if type(iupDialog) == "userdata" then
iupDialog:destroy()
tblName.Dialog = nil -- Prevent future misuse of handle -- 22 Jul 2014
end
end
end -- function DestroyDialogue
local function strDialogueArgs(strArgA,strArgB,comp) -- Compare two argument pairs and return matching pair
local tblArgA = stringx.splitnumbers(strArgA)
local tblArgB = stringx.splitnumbers(strArgB)
local strArgX = tostring(comp(tblArgA[1] or 100,tblArgB[1] or 100))
local strArgY = tostring(comp(tblArgA[2] or 100,tblArgB[2] or 100))
return strArgX.."x"..strArgY
end -- local function strDialogueArgs
function fh.RefreshDialogue(strName) -- Refresh dialogue window size after Font change, etc
local tblName = tblNameFor(strName)
local iupDialog = tblName.Dialog -- Retrieve the dialogue handle
if type(iupDialog) == "userdata" then
iupDialog.Size = iup.NULL
iupDialog.MinSize = iup.NULL -- V3.1
iup.Refresh(iupDialog) -- Refresh window to Natural Size and set as Minimum Size
if not iupDialog.RasterSize then
iupDialog:map()
iup.Refresh(iupDialog)
end
local strSize = iupDialog.NaturalSize or iupDialog.RasterSize -- IUP 3.5 NaturalSize = nil, IUP 3.11 needs NaturalSize -- V3.1
iupDialog.MinSize = strDialogueArgs(iupDialog.MaxSize,strSize,math.min) -- Set Minimum Size to smaller of Maximm Size or Natural/Raster Size -- V3.1
iupDialog.RasterSize = strDialogueArgs(tblName.Raster,strSize,math.max) -- Set Current Size to larger of Current Size or Natural/Raster Size -- V3.1
iup.Refresh(iupDialog)
tblName.Raster = iupDialog.RasterSize
if iupDialog.Visible == "YES" then -- Ensure visible dialogue origin is on screen
tblName.CoordX = math.max(tblName.CoordX,10)
tblName.CoordY = math.max(tblName.CoordY,10) -- Set both coordinates to larger of current value or 10 pixels
if iupDialog.Modal then -- V3.8
if iupDialog.Modal == "NO" then
iupDialog.ZOrder = "BOTTOM" -- Ensure dialogue is subservient to any popup
iupDialog:showxy(tblName.CoordX,tblName.CoordY) -- Use showxy() to reposition main window
else
iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to reposition modal window
end
end
else
iupDialog.BringFront="YES"
end
end
end -- function RefreshDialogue
function fh.AssignAttributes(tblControls) -- Assign the attributes of all controls supplied
local anyFunction = nil
for iupName, tblAttr in pairs ( tblControls or {} ) do
if type(iupName) == "userdata" and type(tblAttr) == "table" then -- Loop through each iup control
local intSkip = 0 -- Skip counter for attributes same for all controls
for intAttr, anyName in ipairs ( tblControls[1] or {} ) do -- Loop through each iup attribute
local strName = nil
local strAttr = nil
local strType = type(anyName)
if strType == "string" then -- Attribute is different for each control in tblControls
strName = anyName
strAttr = tblAttr[intAttr-intSkip]
elseif strType == "table" then -- Attribute is same for all controls as per tblControls[1]
intSkip = intSkip + 1
strName = anyName[1]
strAttr = anyName[2]
elseif strType == "function" then
intSkip = intSkip + 1
anyFunction = anyName
break
end
if type(strName) == "string" and ( type(strAttr) == "string" or type(strAttr) == "function" ) then
local anyRawGet = rawget(fh,strAttr) -- Use rawget() to stop require("pl.strict") complaining
if type(anyRawGet) == "string" then
strAttr = anyRawGet -- Use internal module attribute such as Head or FontBody
elseif type(iupName[strName]) == "string"
and type(strAttr) == "function" then -- Allow string attributes to invoke a function -- V3.7
strAttr = strAttr()
end
iupName[strName] = strAttr -- Assign attribute to control
end
end
end
end
if anyFunction then anyFunction() end -- Perform any control assignment function
end -- function AssignAttributes
-- Font Dialogue Attributes and Functions --
fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default font for Body and Head text
fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold ")
function fh.FontDialogue(tblAttr,strName) -- GUI Font Face & Style Dialogue
tblAttr = tblAttr or {}
strName = strName or "Main"
local isFontChosen = false
local btnFontHead = iup.button { Title="Choose Headings Font and default Colour"; }
local btnFontBody = iup.button { Title="Choose Body text Font and default Colour"; }
local btnCol_Safe = iup.button { Title=" Safe Colour "; }
local btnCol_Warn = iup.button { Title=" Warning Colour "; }
local btnCol_Risk = iup.button { Title=" Risky Colour "; }
local btnDefault = iup.button { Title=" Default Fonts "; }
local btnMinimum = iup.button { Title=" Minimum Size "; }
local btnDestroy = iup.button { Title=" Close Dialogue "; }
local frmSetFonts = iup.frame { Title=" Set Window Fonts & Colours ";
iup.vbox { Alignment="ACENTER"; Margin=fh.Margin; Homogeneous="YES";
btnFontHead;
btnFontBody;
iup.hbox { btnCol_Safe; btnCol_Warn; btnCol_Risk; Homogeneous="YES"; };
iup.hbox { btnDefault ; btnMinimum ; btnDestroy ; Homogeneous="YES"; };
} -- iup.vbox end
} -- iup.frame end
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local dialogFont = iup.dialog { Title=" Set Window Fonts & Colours "; Gap=fh.Gap; Margin=fh.Border; frmSetFonts; }
local tblButtons = { }
local function setDialogues() -- Refresh the Main dialogue -- V4.4
fh.AssignAttributes(tblAttr) -- Assign parent dialogue attributes
fh.RefreshDialogue(strName) -- Refresh parent window size & position and bring infront of other window
fh.RefreshDialogue("Font") -- Refresh Font window size & position and bring infront of parent window
end -- local function setDialogues
local function getFont(strColor) -- Set font button function
local strTitle = " Choose font style & default colour for "..strColor:gsub("Head","Heading").." text "
local strValue = "Font"..strColor -- The font codes below are not recognised by iupFontDlg and result in empty font face!
local strFont = rawget(fh,strValue):gsub(" Black,",","):gsub(" Light, Bold",","):gsub(" Extra Bold,",","):gsub(" Semibold,",",")
local iupFontDlg = iup.fontdlg { Title=strTitle; Color=rawget(fh,strColor); Value=strFont; }
iupFontDlg:popup() -- Popup predefined font dialogue
if iupFontDlg.Status == "1" then
if iupFontDlg.Value:match("^,") then -- Font face missing so revert to original font
iupFontDlg.Value = rawget(fh,strValue)
end
fh[strColor] = iupFontDlg.Color -- Set Head or Body color attribute
fh[strValue] = iupFontDlg.Value -- Set FontHead or FontBody font style
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
setDialogues()
isFontChosen = true
end
end -- local function getFont
local function getColor(strColor) -- Set colour button function
local strTitle = " Choose colour for "..strColor:gsub("Warn","Warning"):gsub("Risk","Risky").." button & message text "
local iupColorDlg = iup.colordlg { Title=strTitle; Value=rawget(fh,strColor); ShowColorTable="YES"; }
iupColorDlg.DialogFrame="YES"
iupColorDlg:popup() -- Popup predefined color dialogue fixed size window
if iupColorDlg.Status == "1" then
fh[strColor] = iupColorDlg.Value -- Set Safe or Warn or Risk color attribute
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
setDialogues()
isFontChosen = true
end
end -- local function getColor
local function setDefault() -- Action for Default Fonts button
fh.Safe = fh.Green
fh.Warn = fh.Magenta
fh.Risk = fh.Red -- Set default colours
fh.Body = fh.Black
fh.Head = fh.Black
fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default fonts for Body and Head text
fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold")
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
setDialogues()
isFontChosen = true
end -- local function setDefault
local function setMinimum() -- Action for Minimum Size button
local tblName = tblNameFor(strName)
local iupDialog = tblName.Dialog -- Retrieve the parent dialogue handle
if type(iupDialog) == "userdata" then
tblName.Raster = "10x10" -- Refresh parent window to Minimum Size & adjust position
fh.RefreshDialogue(strName)
end
local tblFont = tblNameFor("Font")
tblFont.Raster = "10x10" -- Refresh Font window to Minimum Size & adjust position
fh.RefreshDialogue("Font")
end -- local function setMinimum
tblButtons = { { "Font" ; "FgColor" ; "Tip" ; "action" ; {"TipBalloon";"Balloon";} ; {"Expand";"YES";} ; };
[btnFontHead] = { "FontHead"; "Head"; "Choose the Heading text Font Face, Style, Size, Effects, and default Colour"; function() getFont("Head") end; };
[btnFontBody] = { "FontBody"; "Body"; "Choose the Body text Font Face, Style, Size, Effects, and default Colour" ; function() getFont("Body") end; };
[btnCol_Safe] = { "FontBody"; "Safe"; "Choose the colour for Safe operations" ; function() getColor("Safe") end; };
[btnCol_Warn] = { "FontBody"; "Warn"; "Choose the colour for Warning operations"; function() getColor("Warn") end; };
[btnCol_Risk] = { "FontBody"; "Risk"; "Choose the colour for Risky operations" ; function() getColor("Risk") end; };
[btnDefault ] = { "FontBody"; "Safe"; "Restore default Fonts and Colours"; function() setDefault() end; };
[btnMinimum ] = { "FontBody"; "Safe"; "Reduce window to its minimum size"; function() setMinimum() end; };
[btnDestroy ] = { "FontBody"; "Risk"; "Close this dialogue "; function() return iup.CLOSE end; };
[frmSetFonts] = { "FontHead"; "Head"; };
}
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep normal") -- Popup the Set Window Fonts dialogue: "keep normal" : vary size & posn, and remember size & posn
-- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup normal") -- Popup the Set Window Fonts dialogue: "popup normal" : vary size & posn, but redisplayed centred
-- fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep") -- Popup the Set Window Fonts dialogue: "keep" : fixed size, vary posn, and only remember posn
-- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup") -- Popup the Set Window Fonts dialogue: "popup": fixed size, vary posn, but redisplayed centred
dialogFont:destroy()
return isFontChosen
end -- function FontDialogue
local function anyMemoControl(anyName,fgColor) -- Compose any control Title and FgColor
local strName = tostring(anyName) -- anyName may be a string, and fgColor is default FgColor
local tipText = nil
if type(anyName) == "table" then -- anyName may be a table = { Title string ; FgColor string ; ToolTip string (optional); }
strName = anyName[1]
fgColor = anyName[2]:match("%d* %d* %d*") or fgColor
tipText = anyName[3]
end
return strName, fgColor, tipText
end -- local function anyMemoControl
local function anyMemoDialogue(strHead,anyHead,strMemo,anyMemo,...) -- Display framed memo dialogue with buttons
local arg = {...} -- Fix for Lua 5.2+
local intButt = 0 -- Returned value if "X Close" button is used
local tblButt = { [0]="X Close"; } -- Button names lookup table
local strHead, fgcHead, tipHead = anyMemoControl(anyHead or "",strHead)
local strMemo, fgcMemo, tipMemo = anyMemoControl(anyMemo or "",strMemo)
-- Create the GUI labels and buttons
local lblMemo = iup.label { Title=strMemo; FgColor=fgcMemo; Tip=tipMemo; TipBalloon=fh.Balloon; Alignment="ACENTER"; Padding=fh.Margin; Expand="YES"; WordWrap="YES"; }
local lblLine = iup.label { Separator="HORIZONTAL"; }
local iupHbox = iup.hbox { Homogeneous="YES"; }
local btnButt = iup.button { }
local strTop = "YES" -- Make dialogue TopMost -- V3.6
local strMode = "popup"
if arg[1] == "Keep Dialogue" then -- Keep dialogue open for a progress message
strMode = "keep dialogue"
lblLine = iup.label { }
if not arg[2] then strTop = "NO" end -- User chooses TopMost -- V3.6
else
if #arg == 0 then arg[1] = "OK" end -- If no buttons listed then default to an "OK" button
for intArg, anyButt in ipairs(arg) do
local strButt, fgcButt, tipButt = anyMemoControl(anyButt,fh.Safe)
tblButt[intArg] = strButt
btnButt = iup.button { Title=strButt; FgColor=fgcButt; Tip=tipButt; TipBalloon=fh.Balloon; Expand="NO"; MinSize="80"; Padding=fh.Margin; action=function() intButt=intArg return iup.CLOSE end; }
iup.Append( iupHbox, btnButt )
end
end
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local iupMemo = iup.dialog { Title=fh.Plugin..fh.Version..strHead; TopMost=strTop; -- TopMost added -- V3.6
iup.vbox { Alignment="ACENTER"; Gap=fh.Gap; Margin=fh.Margin;
iup.frame { Title=strHead; FgColor=fgcHead; Font=fh.FontHead;
iup.vbox { Alignment="ACENTER"; Font=fh.FontBody; lblMemo; lblLine; iupHbox; };
};
};
}
fh.ShowDialogue("Memo",iupMemo,btnButt,strMode) -- Show popup Memo dialogue window with righthand button in focus (if any)
if strMode == "keep dialogue" then return lblMemo, iupMemo end -- Return label & dialogue controls so message can be changed and dialogue destroyed
iupMemo:destroy()
return intButt, tblButt[intButt] -- Return button number & title that was pressed
end -- local function anyMemoDialogue
function fh.MemoDialogue(anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with "Memo" in frame
return anyMemoDialogue(fh.Head,"Memo",fh.Body,anyMemo,...)
end -- function MemoDialogue
function fh.WarnDialogue(anyHead,anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with heading in frame
return anyMemoDialogue(fh.Warn,anyHead,fh.Warn,anyMemo,...)
end -- function WarnDialogue
function fh.GetRegKey(strKey) -- Read Windows Registry Key Value
local luaShell = luacom.CreateObject("WScript.Shell")
local anyValue = nil
if pcall( function() anyValue = luaShell:RegRead(strKey) end ) then
return anyValue -- Return Key Value if found
end
return nil
end -- function GetRegKey
function fh.PutRegKey(strKey,anyValue,strType) -- Write Windows Registry Key Value
local luaShell = luacom.CreateObject("WScript.Shell")
local strAns = nil
if pcall( function() strAns = luaShell:RegWrite(strKey,anyValue,strType) end ) then
return true
end
return nil
end -- function PutRegKey
local function httpRequest(strRequest) -- Luacom http request protected by pcall() below
local http = luacom.CreateObject("winhttp.winhttprequest.5.1")
http:Open("GET",strRequest,false)
http:Send()
return http.Responsebody
end -- local function httpRequest
function fh.VersionInStore(strPlugin) -- Obtain the Version in Plugin Store by Name only -- V3.9
local strVersion = "0"
if strPlugin then
local strFile = fh.MachinePath.."\\VersionInStore "..strPlugin..".dat"
local intTime = os.time() - 2600000 -- Time in seconds a month ago -- V3.9
local tblAttr, strError = lfs.attributes(strFile) -- Obtain file attributes
if not tblAttr or tblAttr.modification < intTime then -- File does not exist or was modified long ago -- V3.9
local lblMemo, iupMemo = fh.MemoDialogue("Checking for updated version in the Family Historian 'Plugin Store'.","Keep Dialogue")
local strErrFile = fh.MachinePath.."\\VersionInStoreInternetError.dat"
local strRequest ="http://www.family-historian.co.uk/lnk/checkpluginversion.php?name="..strPlugin
local isOK, strReturn = pcall(httpRequest,strRequest)
if not isOK then -- Problem with Internet access
local intTime = os.time() - 36000 -- Time in seconds 10 hours ago
local tblAttr, strError = lfs.attributes(strErrFile) -- Obtain file attributes
if not tblAttr or tblAttr.modification < intTime then -- File does not exist or was modified long ago
fhMessageBox(strReturn.."\n The Internet appears to be inaccessible. ","MB_OK","MB_ICONEXCLAMATION")
end
general.SaveStringToFile(strErrFile,strErrFile) -- Update file modified time
else
general.DeleteFile(strErrFile) -- Delete file if Internet is OK
if strReturn ~= nil then
strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits
general.SaveStringToFile(strVersion,strFile) -- Update file modified time and save version -- V4.1
end
end
iupMemo:destroy()
else
strVersion = general.StrLoadFromFile(strFile) -- Retrieve saved latest version -- V4.1
if #strVersion > 9 then general.DeleteFile(strFile) end
end
end
return strVersion or "0"
end -- function VersionInStore
local function intVersion(strVersion) -- Convert version string to comparable integer
local intVersion = 0
local arrNumbers = {}
strVersion:gsub("(%d+)", function(strDigits) table.insert(arrNumbers,strDigits) end) -- V4.1
for i=1,5 do
intVersion = intVersion * 100 + tonumber(arrNumbers[i] or 0)
end
return intVersion
end -- local function intVersion
function fh.CheckVersionInStore(isMemo) -- Check if later Version available in Plugin Store
local strNewVer = fh.VersionInStore(fh.Plugin:gsub(" %- .*",""))
local strOldVer = fh.Version
if intVersion(strNewVer) > intVersion(strOldVer:match("%D*([%d%.]*)")) then
fh.MemoDialogue("Later Version "..strNewVer.." of this Plugin is available from the 'Plugin Store'.")
elseif isMemo then
fh.MemoDialogue("No later Version of this Plugin is available from the 'Plugin Store'.")
end
end -- function CheckVersionInStore
function fh.PluginDataScope(strScope) -- Set default Plugin Data scope to per-Project, or per-User, or per-Machine
strScope = tostring(strScope):lower()
if strScope:match("mach") then -- Per-Machine
strDefaultScope = "Machine"
elseif strScope:match("user") then -- Per-User
strDefaultScope = "User"
end -- Per-Project is default
end -- function PluginDataScope
local function getPluginDataFileName(strScope) -- Get plugin data filename for chosen scope
local isOK, strDataFile = pcall(fhGetPluginDataFileName,strScope)
if not isOK then strDataFile = fhGetPluginDataFileName() end -- Before V5.0.8 parameter is disallowed and default = CURRENT_PROJECT
return strDataFile
end -- local function getPluginDataFileName
local function getDataFiles(strScope) -- Compose the Plugin Data file & path & root names
local strPluginName = fh.Plugin
local strPluginPlain = stringx.plain(strPluginName)
local strDataRoot = "" -- Plugin data file root name -- V4.2
local strDataPath = "" -- Plugin data folder path name
local strDataFile = getPluginDataFileName(strScope) -- Allow plugins with variant filenames to use same plugin data files
strDataFile = strDataFile:gsub("\\"..strPluginPlain:gsub(" ","_"):lower(),"\\"..strPluginName)
strDataFile = strDataFile:gsub("\\"..strPluginPlain..".+%.[D,d][A,a][T,t]$","\\"..strPluginName..".dat")
if #strDataFile > 0 then -- Standalone GEDCOM path is ""
strDataPath = strDataFile:gsub("\\"..strPluginPlain.."%.[D,d][A,a][T,t]$","")
strDataRoot = strDataPath.."\\"..strPluginName
general.MakeFolder(strDataPath) -- V3.4
end
return strDataFile, strDataPath, strDataRoot
end -- local function getDataFiles
function fh.Initialise(strVersion,strPlugin) -- Initialise the GUI module with optional Version & Plugin name
local strAppData = fhGetContextInfo("CI_APP_DATA_FOLDER")
fh.Plugin = fhGetContextInfo("CI_PLUGIN_NAME") -- Plugin Name from file
fh.Version = strVersion or " " -- Plugin Version
if fh.Version == " " then
local strTitle = "\n@Title is missing"
local strAuthor = "\n@Author is missing"
local strVersion = "\n@Version is missing"
local strPlugin = strAppData.."\\Plugins\\"..fh.Plugin..".fh_lua"
if general.FlgFileExists(strPlugin) then
for strLine in io.lines(strPlugin) do -- Read each line from the Plugin file
strPlugin = strLine:match("^@Title:[\t-\r ]*(.*)")
if strPlugin then
strPlugin = strPlugin:gsub("&&","&")
--? if fh.Plugin:match("^"..strPlugin:gsub("(%W)","%%%1")) then
if fh.Plugin:match("^"..stringx.plain(strPlugin)) then
fh.Plugin = strPlugin -- Prefer Title to Filename if it matches
strTitle = nil
else
strTitle = "\n@Title differs from Filename" -- Report abnormality
end
end
if strLine:match("^@Author:%s*(.*)") then -- Check @Author exists
strAuthor = nil
end
fh.Version = strLine:gsub("^@Version:%D*([%d%.]*)%D*"," %1 ")
if fh.Version ~= strLine then -- Obtain the @Version from Plugin file
strVersion = nil
break
end
end
if strTitle or strAuthor or strVersion then -- Report any header abnormalities
fhMessageBox("\nScript Header: "..fh.Plugin..(strTitle or "")..(strAuthor or "")..(strVersion or ""),"MB_OK","MB_ICONEXCLAMATION")
end
else
fhMessageBox("\nPlugin has not been saved!","MB_OK","MB_ICONEXCLAMATION")
end
end
fh.History = fh.Version -- Version History
fh.Plugin = strPlugin or fh.Plugin -- Plugin Name from argument or default from file
fh.DefaultDialogue() -- Default "Font","Main" dialogues
fh.MachineFile,fh.MachinePath,fh.MachineRoot = getDataFiles("LOCAL_MACHINE") -- Plugin data names per machine
fh.PerUserFile,fh.PerUserPath,fh.PerUserRoot = getDataFiles("CURRENT_USER") -- Plugin data names per user
fh.ProjectFile,fh.ProjectPath,fh.ProjectRoot = getDataFiles("CURRENT_PROJECT") -- Plugin data names per project
fh.FhDataPath = fhGetContextInfo("CI_PROJECT_DATA_FOLDER") -- Paths used by Load/SaveFolder for relative folders -- V4.0
fh.PublicPath = fhGetContextInfo("CI_PROJECT_PUBLIC_FOLDER") -- Public data folder path name -- V4.0
if fh.FhDataPath == "" then
fh.FhDataPath = fh.ProjectPath:gsub("\\Plugin Data$","")
end
if fh.PublicPath == "" then
fh.PublicPath = fh.ProjectPath
fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Plugin Data$","%1")
else
general.MakeFolder(fh.PublicPath) -- V3.4
fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Public$","%1")
end
fh.CalicoPie = strAppData:gsub("\\Calico Pie\\.*","\\Calico Pie") -- Program Data Calico Pie path name
fh.ComputerName = os.getenv("COMPUTERNAME") -- Local PC Computer Name
end -- function Initialise
fh.Initialise() -- Initialise module with default values
return fh
end -- local function iup_gui_v3
local iup_gui = iup_gui_v3() -- To access FH IUP GUI build module
-- Global Functions --
--[[
The following keywords are used throughout in variable names and indexes for each website:
AncestryCo for Ancestry Co
FindMyPast for Find My Past
Fam_Search for Family Search
MyHeritage for My Heritage
Canada_Gov for Canada Government Library & Archives
--]]
--- Preset Global Data Definitions --
function PresetGlobalData()
iup_gui.Gap = "2"
iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V4.5
iup_gui.SetUtf8Mode()
IntYearToday = general.GetYearToday() -- V4.2 -- Used in TblIndividualDetails()
end -- function PresetGlobalData
-- Reset Sticky Settings to Default Values --
function ResetDefaultSettings()
iup_gui.CustomDialogue("Main","0x0") -- Custom "Main" dialogue minimum size & centralisation
iup_gui.CustomDialogue("Font","0x0") -- Custom "Font" dialogue minimum size & centralisation
iup_gui.DefaultDialogue("Bars") -- GUI window rastersize and X & Y co-ordinates for "Main","Font","Bars" dialogues
IntCensusList = 1 -- U.K. Census Records
IntAncestryCo = 1 -- Lookup ancestry.co.uk
IntFindMyPast = 1 -- Lookup findmypast.co.uk
IntFam_Search = 1 -- Lookup familysearch.org -- V4.2
IntMyHeritage = 1 -- Lookup myheritage.com -- V4.2
IntCanada_Gov = 1 -- Lookup canada.ca/en -- V5.2
IntRecordList = 1 -- Ancestors of an Individual
StrPartCensus = "OFF" -- Exclude any partial Census Records toggle -- V4.1
StrMissCensus = "OFF" -- Exclude any missing Census not online -- V4.3
StrShortNames = "OFF" -- Fewer than 2 Primary Name components toggle -- V4.1
StrInLocality = "OFF" -- Ignore Facts for at home or abroad toggle
IntMinimumAge = 0 -- Minimum Age to treat as missing from Census
StrEachCensus = "OFF" -- Minimum Age applied to every Census toggle
StrPolygamous = "OFF" -- Polygamous marriage relationships toggle -- V4.3
end -- function ResetDefaultSettings
-- Load Sticky Settings from File --
function LoadSettings()
iup_gui.LoadSettings("Bars") -- Includes "Main","Font" dialogues and "FontSet" & "History"
IntCensusList = tonumber(iup_gui.LoadGlobal("CensusList",IntCensusList))
IntAncestryCo = tonumber(iup_gui.LoadGlobal("AncestryCo",IntAncestryCo))
IntFindMyPast = tonumber(iup_gui.LoadGlobal("FindMyPast",IntFindMyPast))
IntFam_Search = tonumber(iup_gui.LoadGlobal("Fam_Search",IntFam_Search)) -- V4.2
IntMyHeritage = tonumber(iup_gui.LoadGlobal("MyHeritage",IntMyHeritage)) -- V4.2
IntCanada_Gov = tonumber(iup_gui.LoadGlobal("Canada_Gov",IntCanada_Gov)) -- V5.2
IntRecordList = tonumber(iup_gui.LoadGlobal("RecordList",IntRecordList))
StrPartCensus = tostring(iup_gui.LoadGlobal("PartCensus",StrPartCensus)) -- V4.1
StrMissCensus = tostring(iup_gui.LoadGlobal("MissCensus",StrMissCensus)) -- V4.3
StrShortNames = tostring(iup_gui.LoadGlobal("ShortNames",StrShortNames)) -- V4.1
StrInLocality = tostring(iup_gui.LoadGlobal("InLocality",StrInLocality))
IntMinimumAge = tonumber(iup_gui.LoadGlobal("MinimumAge",IntMinimumAge))
StrEachCensus = tostring(iup_gui.LoadGlobal("EachCensus",StrEachCensus))
StrPolygamous = tostring(iup_gui.LoadGlobal("Polygamous",StrPolygamous)) -- V4.3
SaveSettings() -- Save sticky data settings
end -- function LoadSettings
-- Save Sticky Settings to File --
function SaveSettings()
iup_gui.SaveGlobal("CensusList",IntCensusList)
iup_gui.SaveGlobal("AncestryCo",IntAncestryCo)
iup_gui.SaveGlobal("FindMyPast",IntFindMyPast)
iup_gui.SaveGlobal("Fam_Search",IntFam_Search) -- V4.2
iup_gui.SaveGlobal("MyHeritage",IntMyHeritage) -- V4.2
iup_gui.SaveGlobal("Canada_Gov",IntCanada_Gov) -- V5.2
iup_gui.SaveGlobal("RecordList",IntRecordList)
iup_gui.SaveGlobal("PartCensus",StrPartCensus) -- V4.1
iup_gui.SaveGlobal("MissCensus",StrMissCensus) -- V4.3
iup_gui.SaveGlobal("ShortNames",StrShortNames) -- V4.1
iup_gui.SaveGlobal("InLocality",StrInLocality)
iup_gui.SaveGlobal("MinimumAge",IntMinimumAge)
iup_gui.SaveGlobal("EachCensus",StrEachCensus)
iup_gui.SaveGlobal("Polygamous",StrPolygamous) -- V4.3
iup_gui.SaveSettings("Bars") -- Includes "Main","Font","Help" dialogues and "FontSet" & "History"
end -- function SaveSettings
-- Graphical User Interface --
function GUI_MainDialogue()
-- Create GUI controls
local lblCensusList = iup.label { Alignment="ARIGHT"; Title=" Choose a set of Census Records to check :"; }
local lstCensusList = iup.list { DropDown="YES"; Visible_Items="19"; " U. K. Census Records 1790 - 1939"; " U. S. A. Census Records 1790 - 1950"; " Ireland Census Records 1749 - 1921"; " Canada Census Records 1825 - 1945"; " Australia Census Records 1828 - 1921"; " Germany Census Records 1819 - 1939"; " Denmark Census Records 1787 - 1940"; " Norway Census Records 1801 - 1920"; }
local hbxCensusList = iup.hbox { Homogeneous="YES"; lblCensusList; lstCensusList; }
local tglPartCensus = iup.toggle{ Title=" Exclude any partial Census Records that do not comprise nationwide Government collections ? "; } -- V4.1
local hbxPartCensus = iup.hbox { iup.label { Expand="YES"; }; tglPartCensus; }
local tglMissCensus = iup.toggle{ Title=" Exclude any missing Census Records that have no genealogy lookup web site search available ? "; } -- V4.3
local hbxMissCensus = iup.hbox { iup.label { Expand="YES"; }; tglMissCensus; }
local lblAncestryCo = iup.label { Alignment="ARIGHT"; Title=" Choose an AncestryCo lookup web site :"; }
local lstAncestryCo = iup.list { DropDown="YES"; Visible_Items="19"; " Ancestry U. K. : ancestry.co.uk"; " Ancestry U. S. A. : ancestry.com"; " Ancestry Canada : ancestry.ca"; " Ancestry Australia : ancestry.com.au"; " Ancestry Germany : ancestry.de"; " Ancestry Sweden : ancestry.se"; " Ancestry France : ancestry.fr"; " Ancestry Italy : ancestry.it"; " Ancestry lookup is disabled"; }
local hbxAncestryCo = iup.hbox { Homogeneous="YES"; lblAncestryCo; lstAncestryCo; }
local lblFindMyPast = iup.label { Alignment="ARIGHT"; Title=" Choose a FindMyPast lookup web site :"; }
local lstFindMyPast = iup.list { DropDown="YES"; Visible_Items="19"; " FindMyPast U. K. : findmypast.co.uk"; " FindMyPast U. S. A. : findmypast.com"; " FindMyPast Ireland : findmypast.ie"; " FindMyPast Australia : findmypast.com.au"; " FindMyPast lookup is disabled"; }
local hbxFindMyPast = iup.hbox { Homogeneous="YES"; lblFindMyPast; lstFindMyPast; }
local lblFam_Search = iup.label { Alignment="ARIGHT"; Title=" Choose FamilySearch lookup web site :"; }
local lstFam_Search = iup.list { DropDown="YES"; Visible_Items="19"; " FamilySearch.org : familysearch.org"; " FamilySearch lookup is disabled"; } -- V4.2
local hbxFam_Search = iup.hbox { Homogeneous="YES"; lblFam_Search; lstFam_Search; }
local lblMyHeritage = iup.label { Alignment="ARIGHT"; Title=" Choose MyHeritage lookup web site :"; }
local lstMyHeritage = iup.list { DropDown="YES"; Visible_Items="19"; " MyHeritage.com : myheritage.com"; " MyHeritage lookup is disabled"; } -- V4.2
local hbxMyHeritage = iup.hbox { Homogeneous="YES"; lblMyHeritage; lstMyHeritage; }
local lblCanada_Gov = iup.label { Alignment="ARIGHT"; Title=" Choose Canada Gov lookup web site :"; }
local lstCanada_Gov = iup.list { DropDown="YES"; Visible_Items="19"; " Canada.ca English : canada.ca/en"; " Canada.ca Francais : canada.ca/fr"; " Canada Gov lookup is disabled"; } -- V5.2
local hbxCanada_Gov = iup.hbox { Homogeneous="YES"; lblCanada_Gov; lstCanada_Gov; }
local lblRecordList = iup.label { Alignment="ARIGHT"; Title=" Choose the Individuals to investigate :"; }
local lstRecordList = iup.list { DropDown="YES"; Visible_Items="19"; " All the Ancestors of a chosen person"; " All the Descendants of a chosen person"; " All Ancestors & Descendants of a person"; " All the Relations of a chosen person"; " Anyone via the Select Records dialogue"; }
local hbxRecordList = iup.hbox { Homogeneous="YES"; lblRecordList; lstRecordList; }
local tglShortNames = iup.toggle{ Title=" Exclude each person with too few Primary Name parts to create a worthwhile Census search ? "; } -- V4.1
local hbxShortNames = iup.hbox { iup.label { Expand="YES"; }; tglShortNames; }
local tglInLocality = iup.toggle{ Title=" Use their Facts to decide if each person was at home or abroad on the missing Census dates ? "; }
local hbxInLocality = iup.hbox { iup.label { Expand="YES"; }; tglInLocality; }
local lblMinimumAge = iup.label { Alignment="ARIGHT"; Title=" Minimum Age at Census dates : "; }
local txtMinimumAge = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinValue=IntMinimumAge; SpinMax=99; Size="25"; }
local hbxMinimumAge = iup.hbox { Homogeneous="NO"; lblMinimumAge; txtMinimumAge; }
local tglEachCensus = iup.toggle{ Title=" Minimum Age applies to every Census ? "; RightButton="YES"; }
local hbxEachCensus = iup.hbox { iup.label { Expand="YES"; }; tglEachCensus; }
local vbxMinimumAge = iup.vbox { hbxMinimumAge; hbxEachCensus; Gap="6"; Margin="9x0"; }
local btnRunLookup = iup.button{ Title="Lookup Missing Census Facts"; }
local hbxRunLookup = iup.hbox { Homogeneous="YES"; vbxMinimumAge; btnRunLookup; Gap="2"; }
local tglPolygamous = iup.toggle{ Title=" Recognise polygamous relationships ? "; RightButton="YES"; } -- V4.3
local hbxPolygamous = iup.hbox { iup.label { Expand="YES"; }; tglPolygamous; }
local vbxPolygamous = iup.vbox { hbxPolygamous; Gap="6"; Margin="9x4"; }
local btnRedisplay = iup.button{ Title="Redisplay the Last Lookup Page"; } -- V4.3
local hbxRedisplay = iup.hbox { Homogeneous="YES"; vbxPolygamous; btnRedisplay; Gap="2"; }
local btnDefault = iup.button{ Title="Restore Defaults"; }
local btnSetFont = iup.button{ Title="Set Window Fonts"; }
local btnUpdates = iup.button{ Title="Check for Updates"; } -- V5.3
local btnGetHelp = iup.button{ Title=" Help && Advice"; }
local btnDestroy = iup.button{ Title="Close Plugin"; }
local hbxButtons = iup.hbox { Homogeneous="YES"; btnDefault; btnSetFont; btnUpdates; btnGetHelp; btnDestroy; }
-- Combine all the above controls
local allCont = iup.vbox { Gap="4"; Margin="8x8"; hbxCensusList; hbxPartCensus; hbxMissCensus; hbxAncestryCo; hbxFindMyPast; hbxFam_Search; hbxMyHeritage; hbxCanada_Gov; hbxRecordList; hbxShortNames; hbxInLocality; hbxRunLookup; hbxRedisplay; hbxButtons; } -- V4.3
-- Create dialogue
local dialogMain = iup.dialog { Title=iup_gui.Plugin..iup_gui.Version; allCont; }
local function setControls() -- Reset GUI control values
lstCensusList.Value = IntCensusList
lstAncestryCo.Value = IntAncestryCo
lstFindMyPast.Value = IntFindMyPast
lstFam_Search.Value = IntFam_Search -- V4.2
lstMyHeritage.Value = IntMyHeritage -- V4.2
lstCanada_Gov.Value = IntCanada_Gov -- V5.2
lstRecordList.Value = IntRecordList
tglPartCensus.Value = StrPartCensus -- V4.1
tglMissCensus.Value = StrMissCensus -- V4.3
tglShortNames.Value = StrShortNames -- V4.1
tglInLocality.Value = StrInLocality
txtMinimumAge.SpinValue = IntMinimumAge
tglEachCensus.Value = StrEachCensus
tglPolygamous.Value = StrPolygamous -- V4.3
end -- local function setControls
-- Set other GUI control attributes
local strHelpAdvice = "\n Click the 'Help and Advice' below for details" -- V4.1
local tblControls = { { "Font"; "FgColor"; "Padding"; "Tip"; {"TipBalloon";"Balloon"}; {"Expand";"YES"}; {"help_cb";function() iup_gui.HelpDialogue(0) end}; setControls; };
[lblCensusList] = { "FontBody"; "Body"; "10x4"; "Choose country for Census records to be checked"..strHelpAdvice; };
[lstCensusList] = { "FontBody"; "Safe"; "4x4"; "Choose country for Census records to be checked"..strHelpAdvice; };
[tglPartCensus] = { "FontBody"; "Safe"; "4x0"; "Exclude any partial Census record collections?"..strHelpAdvice; };
[tglMissCensus] = { "FontBody"; "Safe"; "4x0"; "Exclude any missing Census records not online?"..strHelpAdvice; }; -- V4.3
[lblAncestryCo] = { "FontBody"; "Body"; "10x4"; "Choose an Ancestry lookup web site to use"..strHelpAdvice; };
[lstAncestryCo] = { "FontBody"; "Safe"; "4x4"; "Choose an Ancestry lookup web site to use"..strHelpAdvice; };
[lblFindMyPast] = { "FontBody"; "Body"; "10x4"; "Choose a FindMyPast lookup web site to use"..strHelpAdvice; };
[lstFindMyPast] = { "FontBody"; "Safe"; "4x4"; "Choose a FindMyPast lookup web site to use"..strHelpAdvice; };
[lblFam_Search] = { "FontBody"; "Body"; "10x4"; "Choose FamilySearch lookup web site to use"..strHelpAdvice; }; -- V4.2
[lstFam_Search] = { "FontBody"; "Safe"; "4x4"; "Choose FamilySearch lookup web site to use"..strHelpAdvice; };
[lblMyHeritage] = { "FontBody"; "Body"; "10x4"; "Choose MyHeritage lookup web site to use"..strHelpAdvice; }; -- V4.2
[lstMyHeritage] = { "FontBody"; "Safe"; "4x4"; "Choose MyHeritage lookup web site to use"..strHelpAdvice; };
[lblCanada_Gov] = { "FontBody"; "Body"; "10x4"; "Choose Canada Gov lookup web site to use"..strHelpAdvice; }; -- V5.2
[lstCanada_Gov] = { "FontBody"; "Safe"; "4x4"; "Choose Canada Gov lookup web site to use"..strHelpAdvice; };
[lblRecordList] = { "FontBody"; "Body"; "10x4"; "Choose the Individuals to include in lookup search"..strHelpAdvice; };
[lstRecordList] = { "FontBody"; "Safe"; "4x4"; "Choose the Individuals to include in lookup search"..strHelpAdvice; };
[tglShortNames] = { "FontBody"; "Safe"; "4x0"; "Exclude each Individual with fewer than 2 Primary Name parts\n delimited by punctuation and not enclosed in [ brackets ] ?"..strHelpAdvice; };
[tglInLocality] = { "FontBody"; "Safe"; "4x0"; "Use all Facts of each Individual to decide if they were\n at home or abroad on missing Census record dates?"..strHelpAdvice; };
[lblMinimumAge] = { "FontBody"; "Body"; "1x0"; "Select minimum Age for certain Census records"..strHelpAdvice; };
[txtMinimumAge] = { "FontHead"; "Safe"; "1x0"; "Select minimum Age for certain Census records"..strHelpAdvice; };
[tglEachCensus] = { "FontBody"; "Safe"; "4x0"; "Apply the minimum Age to ALL Census records?"..strHelpAdvice; };
[tglPolygamous] = { "FontBody"; "Safe"; "4x0"; "Recognise polygamous marriage relationships?"..strHelpAdvice; }; -- V4.3
[btnRunLookup] = { "FontHead"; "Safe"; "1x6"; "Start the missing Census records lookup search"..strHelpAdvice; };
[btnRedisplay] = { "FontHead"; "Safe"; "1x6"; "Redisplay the last Census records lookup search"..strHelpAdvice; }; -- V4.3
[btnDefault ] = { "FontBody"; "Safe"; "1x1"; "Restore all option settings, and window positions and sizes"; };
[btnSetFont ] = { "FontBody"; "Safe"; "1x1"; "Alter the window interface font styles"; };
[btnUpdates] = { "FontBody"; "Safe"; "1x1"; "Check for a later version in the Plugin Store"; }; -- V5.3
[btnGetHelp ] = { "FontBody"; "Safe"; "1x1"; "Access the online Help and Advice pages"; };
[btnDestroy ] = { "FontBody"; "Risk"; "1x1"; "Close this Plugin"; };
}
iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes
local strFolder = iup_gui.MachinePath.."\\" -- Define the folder for the browser files -- V4.3
local strPlugin = iup_gui.Plugin:lower():gsub(" ","") -- Define lowercase and despaced Plugin name
local strRecent = ""
local intAccess = 0 -- Time in seconds long ago
if general.FlgFileExists(strFolder..strPlugin..".css")
and general.FlgFileExists(strFolder..strPlugin..".js") then
for strEntry in lfs.dir(strFolder) do -- Search for files in the folder
if strEntry ~= "." and strEntry ~= ".." then
local strPath = strFolder..strEntry
local tblAttr, strError = lfs.attributes(strPath) -- Obtain file attributes
if not tblAttr then tblAttr = { mode="attrfail"; error=strError; } end
if tblAttr.mode == "file"
and strEntry:match(strPlugin..".+%.html$") then -- Simple file with matching name?
if tblAttr.access > intAccess then
intAccess = tblAttr.access
strRecent = strPath -- Most recent HTML file
end
end
end
end
end
local function setAttributes(iupItem) -- Set the GUI attributes
if lstAncestryCo[IntAncestryCo]:match("disable")
and lstFindMyPast[IntFindMyPast]:match("disable")
and lstFam_Search[IntFam_Search]:match("disable") -- V4.2
and lstMyHeritage[IntMyHeritage]:match("disable") -- V4.2
and lstCanada_Gov[IntCanada_Gov]:match("disable") then -- V5.2
btnRunLookup.Active = "NO" -- Disable Run Lookup button if all websites disabled
else
btnRunLookup.Active = "YES"
end
if general.FlgFileExists(strRecent) then
btnRedisplay.Active = "YES"
else
btnRedisplay.Active = "NO" -- V4.3 -- Disable Redisplay button if no web page HTML file
end
end -- local function setAttributes
function lstCensusList:action(strText,intItem,intState) -- Action for CensusList dropdown
if intState == 1 then
IntCensusList = intItem
SaveSettings()
setAttributes(lstCensusList)
end
end -- function lstCensusList:action
function lstAncestryCo:action(strText,intItem,intState) -- Action for AncestryCo dropdown
if intState == 1 then
IntAncestryCo = intItem
SaveSettings()
setAttributes(lstAncestryCo)
end
end -- function lstAncestryCo:action
function lstFindMyPast:action(strText,intItem,intState) -- Action for FindMyPast dropdown
if intState == 1 then
IntFindMyPast = intItem
SaveSettings()
setAttributes(lstFindMyPast)
end
end -- function lstFindMyPast:action
function lstFam_Search:action(strText,intItem,intState) -- Action for Fam_Search dropdown
if intState == 1 then
IntFam_Search = intItem
SaveSettings()
setAttributes(lstFam_Search)
end
end -- function lstFam_Search:action
function lstMyHeritage:action(strText,intItem,intState) -- Action for MyHeritage dropdown
if intState == 1 then
IntMyHeritage = intItem
SaveSettings()
setAttributes(lstMyHeritage)
end
end -- function lstMyHeritage:action
function lstCanada_Gov:action(strText,intItem,intState) -- Action for Canada_Gov dropdown -- V5.2
if intState == 1 then
IntCanada_Gov = intItem
SaveSettings()
setAttributes(lstCanada_Gov)
end
end -- function lstCanada_Gov:action
function lstRecordList:action(strText,intItem,intState) -- Action for RecordList dropdown
if intState == 1 then
IntRecordList = intItem
SaveSettings()
setAttributes(lstRecordList)
end
end -- function lstRecordList:action
function tglPartCensus:action(intState) -- Action for exclude partial Census toggle -- V4.1
StrPartCensus = tglPartCensus.Value
SaveSettings()
tglPartCensus.Tip = tglPartCensus.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglPartCensus:action
function tglMissCensus:action(intState) -- Action for exclude missing Census toggle -- V4.3
StrMissCensus = tglMissCensus.Value
SaveSettings()
tglMissCensus.Tip = tglMissCensus.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglMissCensus:action
function tglShortNames:action(intState) -- Action for fewer than 2 Name parts toggle -- V4.1
StrShortNames = tglShortNames.Value
SaveSettings()
tglShortNames.Tip = tglShortNames.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglShortNames:action
function tglInLocality:action(intState) -- Action for In Locality toggle
StrInLocality = tglInLocality.Value
SaveSettings()
tglInLocality.Tip = tglInLocality.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglInLocality:action
function txtMinimumAge:spin_cb(intSpinValue) -- Action for Minimum Age spin value
IntMinimumAge = intSpinValue
end -- function txtMinimumAge:spin_cb
function tglEachCensus:action(intState) -- Action for Each Census toggle
StrEachCensus = tglEachCensus.Value
SaveSettings()
tglEachCensus.Tip = tglEachCensus.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglEachCensus:action
function tglPolygamous:action(intState) -- Action for Polygamous toggle -- V4.3
StrPolygamous = tglPolygamous.Value
SaveSettings()
tglPolygamous.Tip = tglPolygamous.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglPolygamous:action
function btnRunLookup:action() -- Action for Run Lookup button
dialogMain.Active = "NO"
btnRunLookup.Active = "NO" -- V4.1 -- Disable GUI while running lookup
btnRedisplay.Active = "NO"
local strCensusList = lstCensusList[lstCensusList.Value]
local dicLookupSite = { -- V4.3 -- Lookup web sites where element names are used in Template & Census profiles
AncestryCo = lstAncestryCo[lstAncestryCo.Value]:match(" : -([^ ]-ancestry.-)$") or "" ;
FindMyPast = lstFindMyPast[lstFindMyPast.Value]:match(" : -([^ ]-findmypast.-)$") or "" ;
Fam_Search = lstFam_Search[lstFam_Search.Value]:match(" : -([^ ]-familysearch.-)$") or "" ; -- V4.2
MyHeritage = lstMyHeritage[lstMyHeritage.Value]:match(" : -([^ ]-myheritage.-)$") or "" ; -- V4.2
Canada_Gov = lstCanada_Gov[lstCanada_Gov.Value]:match(" : -([^ ]-canada.-)$") or "" ; -- V5.2
}
local strRecordList = lstRecordList[lstRecordList.Value]:match(" (Ancestors.- of )")
or lstRecordList[lstRecordList.Value]:match(" (Descendant.- of )")
or lstRecordList[lstRecordList.Value]:match(" (Relations.- of )") -- Extract Individuals to check from GUI option
local dicUserOption = { -- V4.3 User optional mode settings
PartCensus = StrPartCensus == "ON" ; -- V4.1
MissCensus = StrMissCensus == "ON" ; -- V4.3
ShortNames = StrShortNames == "ON" ; -- V4.1
InLocality = StrInLocality == "ON" ;
MinimumAge = IntMinimumAge ;
EachCensus = StrEachCensus == "ON" ;
Polygamous = StrPolygamous == "ON" ; -- V4.3
}
RunLookup(strCensusList,dicLookupSite,strRecordList,dicUserOption)
return iup.CLOSE
end -- function btnRunLookup:action
function btnRedisplay:action() -- Action for Redisplay button -- V4.3
dialogMain.Active = "NO"
btnRedisplay.Active = "NO"
local isOK, intErrorCode, strErrorText = fhShellExecute(strRecent)
return iup.CLOSE
end -- function btnRedisplay:action
function btnDefault:action() -- Action for Restore Defaults button
ResetDefaultSettings()
setControls() -- Reset controls & redisplay Main dialogue
iup_gui.ShowDialogue("Main")
iup_gui.DefaultDialogue() -- V5.3
SaveSettings() -- Save sticky data settings
setAttributes(btnDefault)
end -- function btnDefault:action
function btnSetFont:action() -- Action for Set Window Font button
btnSetFont.Active = "NO"
iup_gui.FontDialogue(tblControls)
SaveSettings() -- Save sticky data settings
btnSetFont.Active = "YES"
end -- function btnSetFont:action
function btnSetFont:button_cb(intButton,intPress) -- Action for mouse right-click on Set Window Fonts button
if intButton == iup.BUTTON3 and intPress == 0 then
iup_gui.BalloonToggle() -- Toggle tooltips Balloon mode
end
end -- function btnSetFont:button_cb
local function doExecute(strExecutable, strParameter) -- Invoke FH Shell Execute API -- V4.5
local function ReportError(strMessage)
iup_gui.WarnDialogue( "Shell Execute Error",
"ERROR: "..strMessage.." :\n"..strExecutable.."\n"..strParameter.."\n\n",
"OK" )
end -- local function ReportError
return general.DoExecute(strExecutable, strParameter, ReportError)
end -- local function doExecute
function btnUpdates:action() -- Action for Check for Updates button -- V5.3
local strFile = iup_gui.MachinePath.."\\VersionInStore Lookup Missing Census Facts.dat"
general.DeleteFile(strFile)
iup_gui.CheckVersionInStore(true) -- Notify if later Version
end -- function btnUpdates:action
local strHelp = "https://pluginstore.family-historian.co.uk/page/help/lookup-missing-census-facts"
function btnGetHelp:action() -- Action for Help & Advice button -- V4.5
doExecute( strHelp )
fhSleep(3000,500)
dialogMain.BringFront="YES"
end -- function btnGetHelp:action
function btnDestroy:action() -- Action for Close button
return iup.CLOSE
end -- function btnDestroy:action
setAttributes(btnDefault)
iup_gui.ShowDialogue("Main",dialogMain,btnDestroy) -- Display Main GUI Dialogue and optionally Version History Help
end -- function GUI_MainDialogue
-- Java Script --
StrWebPageJS =
[[
/*
Table sorting script by Joost de Valk, check it out at http://www.joostdevalk.nl/code/sortable-table/.
Based on a script from http://www.kryogenix.org/code/browser/sorttable/.
Distributed under the MIT license: http://www.kryogenix.org/code/browser/licence.html .
Copyright (c) 1997-2007 Stuart Langridge, Joost de Valk.
Version 1.5.7
*/
/* You can change these values */
var image_path = "http://www.fhug.org.uk/programs/js/sortable/";
var image_up = "arrow-up.gif";
var image_down = "arrow-down.gif";
var image_none = "arrow-none.gif";
var europeandate = true;
var alternate_row_colors = false;
/* MBT
For debugging use console.log("x:",x);
In Firefox use Tools > Web Developer > Web Console Ctrl+Shft+K or Browsr Console Ctrl+Shft+J
*/
/* Don't change anything below this unless you know what you're doing */
addEvent(window, "load", sortables_init);
var SORT_COLUMN_INDEX;
var SORT_DIRECTION;
var thead = false;
function sortables_init() {
// Find all tables with class sortable and make them sortable
if (!document.getElementsByTagName) return;
tbls = document.getElementsByTagName("table");
for (ti=0;ti 0) {
if (t.tHead && t.tHead.rows.length > 0) {
var firstRow = t.tHead.rows[t.tHead.rows.length-1];
thead = true;
} else {
var firstRow = t.rows[0];
}
}
if (!firstRow) return;
// We have a first row: assume it's the header, and make its contents clickable links
for (var i=0;i'+txt+'
';
}
}
if (alternate_row_colors) {
alternate(t);
}
}
function ts_getInnerText(el) {
if (typeof el == "string") return el;
if (typeof el == "undefined") { return el };
if (el.innerText) return el.innerText; //Not needed but it is faster
var str = "";
var cs = el.childNodes;
var l = cs.length;
for (var i = 0; i < l; i++) {
switch (cs[i].nodeType) {
case 1: //ELEMENT_NODE
str += ts_getInnerText(cs[i]);
break;
case 3: //TEXT_NODE
str += cs[i].nodeValue;
break;
}
}
return str;
}
function ts_resortTable(lnk, clid) {
var span;
for (var ci=0;ci ';
newRows.reverse();
span.setAttribute('sortdir','up');
} else {
ARROW = '
';
span.setAttribute('sortdir','down');
}
// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
// don't do sortbottom rows
for (i=0; i ';
}
}
}
span.innerHTML = ARROW;
alternate(t);
}
function getParent(el, pTagName) {
if (el == null) {
return null;
} else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) {
return el;
} else {
return getParent(el.parentNode, pTagName);
}
}
function sort_date(date) {
// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
if (date == "") {
if (SORT_DIRECTION == 'down') { // MBT force blank cells to bottom depending on sort 'down' or 'up'
return "00000000";
} else {
return "99999999";
}
}
var dt = "00000000";
var regexp = /^(.*[\s\-]\d\d\d\d)/; // MBT
var result = regexp.exec(date); // MBT remove trailing text after 4 digit year
date = result[1]; // MBT
if (date.length == 11) {
var mtstr = date.substr(3,3);
mtstr = mtstr.toLowerCase();
switch(mtstr) {
case "jan": var mt = "01"; break;
case "feb": var mt = "02"; break;
case "mar": var mt = "03"; break;
case "apr": var mt = "04"; break;
case "may": var mt = "05"; break;
case "jun": var mt = "06"; break;
case "jul": var mt = "07"; break;
case "aug": var mt = "08"; break;
case "sep": var mt = "09"; break;
case "oct": var mt = "10"; break;
case "nov": var mt = "11"; break;
case "dec": var mt = "12"; break;
default: var mt = "00"; // MBT removed comment to enable default and cope with "bef" & "aft" & "___"
}
dt = date.substr(7,4)+mt+date.substr(0,2);
return dt;
} else if (date.length == 10) {
if (europeandate == false) {
dt = date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
return dt;
} else {
dt = date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
return dt;
}
} else if (date.length == 8) {
var yr = date.substr(6,2);
if (parseInt(yr) < 50) {
yr = '20'+yr;
} else {
yr = '19'+yr;
}
if (europeandate == true) {
dt = yr+date.substr(3,2)+date.substr(0,2);
return dt;
} else {
dt = yr+date.substr(0,2)+date.substr(3,2);
return dt;
}
} else if (date.length == 5) {
dt = date.substr(1,4)+"0000"; // MBT " yyyy"
return dt;
}
return dt;
}
function ts_sort_date(a,b) {
var dt1 = sort_date(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
var dt2 = sort_date(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
if (dt1==dt2) {
return 0;
}
if (dt1
{TitleName}
{TitleName}
The following people appear to have no Census information when they could be alive.
People with no life dates recorded are assessed on their estimated life dates.
Women who are married at the time of a Census are shown with their married name if available.
Where there is no marriage date the Family Date is taken from the 1st child's birth date if any.
The Plugin Help & Advice for Frequently Asked Questions has tips on refining lookup search filters for each website.
No. {AncestryCo}{FindMyPast}{Fam_Search}{MyHeritage}{Canada_Gov}
Individual's Name
Birth Year
Life Dates
Spouse Family at Census
Family Date
Father's Name
Mother's Name
]]
StrWebTailTemplate = -- Grid inserted between StrWebPageTemplate and StrWebTailTemplate -- V4.3
[[
]]
StrWebHeadTemplate = -- Replaces website headings in StrWebPageTemplate above
[[
{WebAddress} ]]
StrWebLineTemplate = -- Replaces {WebGrid} in StrWebPageTemplate above
[[
{ItemNumber} {AncestryCo}{FindMyPast}{Fam_Search}{MyHeritage}{Canada_Gov}
{Individual}
{Birth_Year}
{Life_Dates}
{FamilyName}
{FamilyDate}
{FatherName}
{MotherName}
]]
StrMissingTemplate = -- Replaces website cells in StrWebLineTemplate above when no hyperlink
[[
{CensusLink}{LocaleLink} ]] -- V4.3 " n/a" not now needed as display is gray
-- {CensusYear} n/a ]]
StrWebSiteTemplate = -- Replaces website cells in StrWebLineTemplate above with a hyperlink -- V4.2
[[
{CensusLink}{LocaleLink} ]]
-- These templates are derived by peforming manual online searches and copying the required URL components.
TblTemplate = { -- Replaces {WebSiteCol} in StrWebSiteTemplate above with a URL -- V4.2
AncestryCo = { -- AncestryCo templates updated -- V4.7
-- e.g. https://www.ancestry.co.uk/search/categories/1911uki/?name=Esther_ANDERSON -- or /collections/7884/
-- Birth &birth=1840_london-england&birth_x=5-0-0
-- Gender &gender=f
-- Spouse &spouse=Iain_ANDERSON
-- Father &father=Laurie_CAMERON
-- Mother &mother=Allison_WATSON
Lookup = [[https://www.{WebAddress}/search/{RecordCode}/?name={GivenNames}_{TheSurname}{Birth_Data}{Gender_Sex}{Relatives}]] ;
Birth = [[&birth={Birth_Year}_{BirthPlace}&birth_x={Birth_Span}-0-0]] ; -- Date Exact ±10 often gives more results than Broad!
Gender = [[&gender={Gender_Sex}]] ;
Spouse = [[&spouse={GivenNames}_{TheSurname}]] ;
Mother = [[&mother={GivenNames}_{TheSurname}]] ;
Father = [[&father={GivenNames}_{TheSurname}]] ;
};
FindMyPast = { -- FindMyPast tempates updated -- V4.7
-- e.g. https://www.findmypast.co.uk/search/results?datasetname=1911+census+for+england+%26+wales&sid=103&firstname=esther&firstname_variants=true&lastname=anderson&lastname_variants=true
-- Birth &yearofbirth=1911&yearofbirth_offset=15
-- Gender &gender=female
-- Spouse &spousefirstname=iain&spousefirstname_variants=true&spouselastname=anderson&spouselastname_variants=true
-- Other &firstnamesother=iain&firstnamesother_variants=true&lastnamesother=anderson&lastnamesother_variants=true
Lookup = [[https://www.{WebAddress}/search/results?datasetname={RecordCode}&firstname={GivenNames}&firstname_variants=true&lastname={TheSurname}&lastname_variants=true{Birth_Data}{Gender_Sex}{Relatives}]] ;
Birth = [[&yearofbirth={Birth_Year}&yearofbirth_offset={Birth_Span}]] ;
Gender = [[&gender={Gender_Sex}]] ;
Spouse = [[&spousefirstname={GivenNames}&spousefirstname_variants=true&spouselastname={TheSurname}&spouselastname_variants=true]] ;
Other = [[&firstnamesother={GivenNames}&firstnamesother_variants=true&lastnamesother={TheSurname}&lastnamesother_variants=true]] ;
};
Fam_Search = { -- FamilySearch templates updated -- V2.6
-- e.g. https://www.familysearch.org/search/record/results?count=20&offset=0&f.recordType=3&f.collectionId=1921547&q.givenName=Esther&q.surname=ANDERSON
-- Birth &q.birthLikeDate.from=1900&q.birthLikeDate.to=1924&q.birthLikePlace=
-- Gender &c.sex=on&f.sex=female
-- Spouse &q.spouseGivenName=Iain&q.spouseSurname=ANDERSON
-- Mother &q.motherGivenName=Allison&q.motherSurname=WATSON
-- Father &q.fatherGivenName=Laurie&q.fatherSurname=CAMERON
Lookup = [[https://www.{WebAddress}/search/record/results?count=20&offset=0&f.recordType=3{RecordCode}&q.givenName={GivenNames}&q.surname={TheSurname}{Birth_Data}{Gender_Sex}{Relatives}]] ;
Birth = [[&q.birthLikeDate.from={Birth_Year}&q.birthLikeDate.to={Birth_Span}&q.birthLikePlace={BirthPlace}]] ; -- {Birth_Year} is earliest year; {Birth_Span} is latest year
Gender = [[&c.sex=on&f.sex={Gender_Sex}]] ;
Spouse = [[&q.spouseGivenName={GivenNames}&q.spouseSurname={TheSurname}]] ;
Mother = [[&q.motherGivenName={GivenNames}&q.motherSurname={TheSurname}]] ; -- or {MaidenName} ?
Father = [[&q.fatherGivenName={GivenNames}&q.fatherSurname={TheSurname}]] ;
};
MyHeritage = { -- MyHeritage templates updated -- V4.7 StrBuildLookup(...) replaces space and dot in Names by '%2F3' Low Ring
-- e.g. https://www.myheritage.com/research/collection-10446/1911-england-wales-census?s=1&formId=&formMode=1&useTranslation=1&exactSearch=&action=query&view_mode=tabular&p=1&qname=Name+fn.Esther+ln.ANDERSON
-- Gender +g.F
-- Birth &qevents-event1=Event+et.birth+ey.1911&qevents-any/1event_1=Event+et.any+ep.+epmo.similar&qevents=List
-- Spouse &qrelative_relativeName=Name+fn.Iain+ln.ANDERSON&qrelatives-relative=Relative+rt.spouse+rn.*qrelative_relativeName
-- Mother &qaddRelative_1_addRelativeName=Name+fn.Allison+ln.WATSON&qrelatives-addRelative_1=Relative+rt.mother+rn.*qaddRelative_1_addRelativeName
-- Father &qaddRelative_2_addRelativeName=Name+fn.Laurie+ln.CAMERON&qrelatives-addRelative_2=Relative+rt.father+rn.*qaddRelative_2_addRelativeName&qrelatives=List
Lookup = [[https://www.{WebAddress}/research/collection-{RecordCode}?s=1&formId=&formMode=1&useTranslation=1&exactSearch=&action=query&view_mode=tabular&p=1&qname=Name+fn.{GivenNames}+ln.{TheSurname}{Gender_Sex}{Birth_Data}{Relatives}&qrelatives=List]] ;
Birth = [[&qevents-event1=Event+et.birth+ey.{Birth_Year}&qevents-any/1event_1=Event+et.any+ep.{BirthPlace}+epmo.similar&qevents=List]] ; -- +me.true+mer.{Birth_Span} but only 1, 2, 5, 10, 20 allowed, so better to allow flexible match
Gender = [[+g.{Gender_Sex}]] ;
Spouse = [[&qrelative_relativeName=Name+fn.{GivenNames}+ln.{TheSurname}&qrelatives-relative=Relative+rt.spouse+rn.*qrelative_relativeName]] ;
Mother = [[&qaddRelative_1_addRelativeName=Name+fn.{GivenNames}+ln.{TheSurname}&qrelatives-addRelative_1=Relative+rt.mother+rn.*qaddRelative_1_addRelativeName]] ;
Father = [[&qaddRelative_2_addRelativeName=Name+fn.{GivenNames}+ln.{TheSurname}&qrelatives-addRelative_2=Relative+rt.father+rn.*qaddRelative_2_addRelativeName]] ;
};
Canada_Gov = { -- Canada Gov templates updated -- V5.2
-- e.g. https://recherche-collection-search.bac-lac.gc.ca/eng/Home/Result?DataSource=Genealogy|Census&ApplicationCode=29&FirstName=Esther&LastName=ANDERSON&YearOfBirth=1900-1924&PlaceOfBirth=Canada&DataSourceSel=Genealogy|Census&SEARCH_TYPE=SEARCH_CENSUS_ADVANCED&
-- Gender &GenderCode=2
-- Birth YearOfBirth=1900-1924&PlaceOfBirth=Canada
-- https://recherche-collection-search.bac-lac.gc.ca/fra/accueil/resultat?DataSource=Genealogy%7CCensus&ApplicationCode=121&DataSourceSel=Genealogy%7CCensus&SEARCH_TYPE=SEARCH_CENSUS_ADVANCED
Lookup = [[https://recherche-collection-search.bac-lac.gc.ca/{WebAddress}?DataSource=Genealogy%7CCensus&ApplicationCode={RecordCode}&FirstName={GivenNames}&LastName={TheSurname}{Gender_Sex}{Birth_Data}{Relatives}&DataSourceSel=Genealogy%7CCensus&SEARCH_TYPE=SEARCH_CENSUS_ADVANCED&]] ;
Birth = [[&YearOfBirth={Birth_Year}-{Birth_Span}&PlaceOfBirth={BirthPlace}]] ;
Gender = [[&GenderCode={Gender_Sex}]] ;
};
}
-- End Web Page Templates --
-- Build Census Profile Table for chosen Region and Records --
function TblCensusProfile(strCensusList,dicLookupSite,dicUserOption)
-- strCensusList Chosen set of Census entries
-- dicLookupSite Lookup web site details -- V4.3
-- dicUserOption User optional mode settings -- V4.3
-- PartCensus Partial Census Records mode -- V4.1
-- MissCensus Missing Census Records mode -- V4.3
-- Polygamous Polygamy recognised mode -- V4.3
--[[
Year Replaces {CensusYear} in Templates, and used to check if Census Event exists
When Replaces {CensusLink} in Template hyperlinks and converted to Date below
Date FH Simple Date Object determines if Individual was alive and in Locality at Census
Part Boolean for partial records that are not nationwide Goverment collections -- V4.1
Child Boolean for children included in Census used with Minimum Age setting
Birth Boolean for birth year included in Census records and Templates
Gender Boolean for gender sex included in Census records and Templates
Spouse Boolean for "spouse****name=" allowed in FindMyPast Template
Other Boolean for "****namesother=" allowed in FindMyPast Template
Locale Names of places to determine if Individual was in Locality at Census
.Record Replaces {RecordCode} in Web Site Template to choose Census Year records -- V4.2
.Locale Replaces {LocaleLink} in Web Site Template hyperlinks for subset records -- V4.2
( Nil entry in .Record & .Locale columns indicate no website records & no locale )
--]]
local strEngland_Wales = "England,Wales,Isle of Man,Jersey,Guernsey,Channel Islands" -- V4.3
local strUnitedKingdom = strEngland_Wales .. ",Scotland,UK,U.K." -- V4.3
local strUSA_1890_CivilWarVeterans = "1890+u.s.+census%2c+civil+war+union+veterans+and+widows"
local tblProfile = { }
local tblColumn = { "Year"; "When"; "Part"; "Child"; "Birth"; "Gender"; "Spouse"; "Other"; "Locale"; "AncestryCo" ; "FindMyPast" ; "Fam_Search" ; "MyHeritage" ; "Canada_Gov" ; }
local tblLookup = {
["U. K."] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1790; " 1790"; true ; true ; true ; false ; false ; false; "Corfe Castle,Corfe,Dorset" ; { " " ; "Corfe Castle" }; { "corfe+castle+and+district+1790+census" ; "Corfe Castle" }; { " " ; "Corfe Castle" }; { " " ; "Corfe Castle" }; { " " ; "Corfe Castle" }; };
{ 1801; " 1801"; true ; true ; false; false ; false ; false; "Dartford,Kent" ; { " " ; "Dartford, Kent" }; { "1801+kent%2c+dartford+census" ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; };
{ 1821; " 1821"; true ; false; false; false ; false ; false; "Dartford,Kent" ; { " " ; "Dartford, Kent" }; { "1821+kent%2c+dartford+census" ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; };
{ 1831; "30 May 1831"; true ; true ; false; false ; false ; false; "Nether Hallam,Hallam,Sheffield" ; { " " ; "Nether Hallam" }; { "nether+hallam%2c+sheffield+1831+census" ; "Nether Hallam" }; { " " ; "Nether Hallam" }; { " " ; "Nether Hallam" }; { " " ; "Nether Hallam" }; };
{ 1841; " 6 Jun 1841"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1841uki" }; { "1841+england%2c+wales+%26+scotland+census" }; { "1493745 2016000" }; { "10150/1841-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1851; "30 Mar 1851"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1851uki" }; { "1851+england%2c+wales+%26+scotland+census" }; { "2563939 2028673" }; { "10151/1851-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1861; " 7 Apr 1861"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1861uki" }; { "1861+england%2c+wales+%26+scotland+census" }; { "1493747 2028677" }; { "10152/1861-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1871; " 2 Apr 1871"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1871uki" }; { "1871+england%2c+wales+%26+scotland+census" }; { "1538354 2028678" }; { "10153/1871-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1881; " 3 Apr 1881"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1881uki" ; "(free index)" }; { "1881+england%2c+wales+%26+scotland+census" ; "(free index)" }; { "2562194 2046756" }; { "10154/1881-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1891; " 5 Apr 1891"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1891uki" }; { "1891+england%2c+wales+%26+scotland+census" }; { "1865747 2046943" }; { "10155/1891-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1901; "31 Mar 1901"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1901uki" }; { "1901+england%2c+wales+%26+scotland+census" }; { "1888129" ; "England&Wales" }; { "10156/1901-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1911; " 2 Apr 1911"; false; true ; true ; true ; false ; true ; strEngland_Wales ; { "categories/1911uki" ; "England&Wales" }; { "1911+census+for+england+%26+wales" ; "England&Wales" }; { "1921547" ; "England&Wales" }; { "10446/1911-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1911; " 2 Apr 1911"; false; true ; true ; true ; false ; true ; "Scotland" ; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; }; -- 1911 Scotland Census not available -- V4.3
{ 1921; "19 Jun 1921"; false; true ; true ; true ; false ; true ; strEngland_Wales ; { "categories/1921uki" ; "England&Wales" }; { "1921+census+of+england+%26+wales" ; "England&Wales" }; { " " ; "England&Wales" }; { " " ; "England&Wales" }; { " " ; "England&Wales" }; }; -- 1921 UK Census is partly available -- V4.7 -- V5.1
{ 1921; "19 Jun 1921"; false; true ; true ; true ; false ; true ; "Scotland" ; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; }; -- 1921 Scotland Census not available -- V4.7
{ 1939; "29 Sep 1939"; false; true ; true ; true ; false ; true ; strEngland_Wales ; { "collections/61596" ; "Register E&W" }; { "1939+register" ; "Register E&W" }; { " " ; "Register E&W" }; { "10678/1939-register-of-england-wales" ; "Register E&W" }; { " " ; "Register E&W" }; }; -- 1939 Register England & Wales -- V3.9-- V4.3 -- V5.1
-- { 1951; " 8 Apr 1951"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { }; { }; { }; { }; { }; }; -- 1951 UK Census not yet available -- V4.3
};
["U. S. A."] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1790; " 2 Aug 1790"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/5058" }; { "us+census+1790" }; { "1803959" }; { "10120/1790-united-states-federal-census" }; { }; };
{ 1800; " 4 Aug 1800"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/7590" }; { "us+census+1800" }; { "1804228" }; { "10121/1800-united-states-federal-census" }; { }; }; -- V4.2 added 'ancestry'
{ 1810; " 6 Aug 1810"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/7613" }; { "us+census+1810" }; { "1803765" }; { "10122/1810-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1820; " 7 Aug 1820"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/7734" }; { "us+census+1820" }; { "1803955" }; { "10123/1820-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1830; " 1 Jun 1830"; false; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/8058" }; { "us+census+1830" }; { "1803958" }; { "10125/1830-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1840; " 1 Jun 1840"; false; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/8057" }; { "us+census+1840" }; { "1786457" }; { "10124/1840-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1850; " 1 Jun 1850"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/8054" }; { "us+census+1850" }; { "1401638 1420440 1420441" }; { "10126/1850-united-states-federal-census" }; { }; }; -- V4.2 " Gender = true
{ 1860; " 1 Jun 1860"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7667" }; { "us+census+1860" }; { "1473181" }; { "10127/1860-united-states-federal-census" }; { }; }; -- V4.2 " Gender = true
{ 1870; " 1 Jun 1870"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7163" }; { "us+census+1870" }; { "1438024" }; { "10128/1870-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1880; " 1 Jun 1880"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/6742" ; "(free)" }; { "us+census+1880" }; { "1417683" }; { "10129/1880-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1890; " 1890"; true ; false; false; true ; false ; false; "America,USA,U.S.A." ; { "collections/8667" ; "Veterans" }; { strUSA_1890_CivilWarVeterans ; "Veterans" }; { }; { }; { }; };
{ 1890; " 2 Jun 1890"; true ; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { "collections/5445" }; { "us+census+1890" }; { "1610551" }; { "10130/1890-united-states-federal-census" }; { }; }; -- V4.2 Child & Birth & Gender = true
{ 1900; " 1 Jun 1900"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7602" }; { "us+census+1900" }; { "1325221" }; { "10131/1900-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1910; "15 Apr 1910"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7884" }; { "us+census+1910" }; { "1727033" }; { "10132/1910-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1920; " 1 Jan 1920"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/6061" }; { "us+census+1920" }; { "1488411" }; { "10133/1920-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1930; " 1 Apr 1930"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/6224" }; { "us+census+1930" }; { "1810731 1821205" }; { "10134/1930-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1940; " 1 Apr 1940"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/2442" ; "(free)" }; { "us+census+1940" }; { "2000219" }; { "10053/1940-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1950; " 1 Apr 1950"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/62308" ; "(free)" }; { }; { }; { "11006/1950-united-states-federal-census" ; "(free)" }; { }; }; -- V4.7 Gender = true
--[==[ for testing
{ 1957; " 1 Apr 1957"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1958; " 1 Apr 1958"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1960; " 1 Apr 1960"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1970; " 1 Apr 1970"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1980; " 1 Apr 1980"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1990; " 1 Apr 1990"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 2000; " 1 Apr 2000"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 2005; " 1 Apr 2005"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 2010; " 1 Apr 2010"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
--]==]
};
["Ireland"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1749; " 1749"; true ; false; false; false ; false ; false; "Ireland,Eire,Elphin" ; { }; { "the+census+of+elphin+1749" ; "Elphin" }; { }; { }; { }; };
{ 1766; " 1766"; true ; false; false; false ; false ; false; "Ireland,Eire" ; { "collections/5990" ; "Religious" }; { }; { }; { }; { }; };
{ 1821; "28 May 1821"; true ; true ; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/62025" }; { "ireland+census+1821-1851" }; { "2345228" }; { }; { }; };
{ 1831; "30 Mar 1831"; true ; false; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/62025" }; { "ireland+census+1821-1851" }; { "2334949" }; { }; { }; };
{ 1841; " 6 Jun 1841"; true ; false; true ; true ; false ; false; "Ireland,Eire" ; { "collections/70792" }; { "ireland+census+search+forms+1841+%26+1851" }; { "2346276 2346275" }; { }; { }; };
{ 1851; "30 Mar 1851"; true ; false; true ; true ; false ; false; "Ireland,Eire" ; { "collections/70792" }; { "ireland+census+search+forms+1841+%26+1851" }; { "2340880 2346275" }; { }; { }; };
{ 1851; "30 Mar 1851"; true ; false; true ; false ; false ; false; "Ireland,Eire,Cork,Dublin" ; { "collections/48535" ; "Cork" }; { "the+1851+dublin+city+census" ; "Dublin" }; { }; { }; { }; };
{ 1901; "31 Mar 1901"; false; true ; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/70667" }; { "ireland+census+1901" }; { "1626180" }; { "10199/1901-ireland-census" }; { }; }; -- V4.2 Gender = true
{ 1911; " 2 Apr 1911"; false; true ; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/70564" }; { "ireland+census+1911" }; { "2854327" }; { "10198/1911-ireland-census" }; { }; }; -- V4.2 Gender = true
{ 1921; "19 Jun 1921"; false; true ; true ; true ; false ; true ; "Ireland,Eire" ; { }; { }; { }; { }; { }; }; -- 1921 Census not yet available -- V4.3
};
["Canada"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1825; " 1825"; true ; false; false; false ; false ; false; "Canada" ; { "collections/9807" }; { "lower+canada+census+1825" }; { "1834346" }; { "30265/1825-canada-lower-canada-census" }; { "121" }; };
{ 1831; " 1831"; true ; false; false; false ; false ; false; "Canada" ; { }; { }; { "1834329" }; { }; { "122" }; };
{ 1842; " 1842"; true ; false; false; false ; false ; false; "Canada" ; { "collections/9808" }; { "lower+canada+census+1842" }; { "1834340 1834342" }; { "30264/1842-canada-lower-canada-census" }; { "123~124" }; };
{ 1851; "12 Jan 1852"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/1061" }; { "canada+census+1851" ; "(free)" }; { "1325192" }; { "10522/1851-canada-census" }; { "26" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1861; "14 Jan 1861"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/1570" }; { "canada+census+1861" ; "(free)" }; { "1325208 1460163 1460164 1460172 1460173" }; { "10521/1861-canada-census" }; { "120" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1870; "16 Jun 1870"; true ; true ; true ; true ; false ; true ; "Canada,Manitoba" ; { }; { }; { }; { }; { "125" }; }; -- V5.2
{ 1871; " 2 Apr 1871"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/1578" }; { "canada+census+1871" ; "(free)" }; { "1551612 1554429" }; { "10520/1871-canada-census" }; { "2~29" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1881; " 4 Apr 1881"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/1577" }; { "canada+census+1881" ; "(free)" }; { "1804541" }; { "10441/1881-canada-census" }; { "16" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1891; " 6 Apr 1891"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/1274" }; { "canada+census+1891" ; "(free)" }; { "1583536" }; { "10440/1891-canada-census" }; { "27" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1901; "31 Mar 1901"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/8826" }; { "canada+census+1901" ; "(free)" }; { "1584557" }; { "10448/1901-canada-census" }; { "28" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1906; "24 Jun 1906"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/8827" }; { }; { "1584925" }; { "10457/1906-canada-census-of-alberta-saskatchewan-manitoba"}; { " 3" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1911; " 1 Jun 1911"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/8947" }; { "canada+census+1911" ; "(free)" }; { "2143998" }; { "10447/1911-canada-census" }; { " 4" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1916; " 1 Jun 1916"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/1556" }; { }; { "1529118" }; { "10458/1916-canada-census-of-alberta-saskatchewan-manitoba"}; { "30" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1921; " 1 Jun 1921"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/8991" }; { }; { }; { "10690/1921-canada-census" }; { "137" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1921; " 1921"; true ; true ; true ; true ; false ; false; "Canada,Newfoundland" ; { "collections/61491" ; "Newfoundland" }; { }; { "2226517" ; "Newfoundland" }; { }; { }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1926; " 1 Jun 1926"; true ; true ; true ; true ; false ; true ; "Canada" ; { }; { }; { }; { }; { "146" }; }; -- V5.2
{ 1931; " 1 Jun 1931"; false; true ; true ; true ; false ; true ; "Canada" ; { }; { }; { }; { }; { "1008" }; }; -- V5.2
{ 1935; " 1935"; true ; true ; true ; true ; false ; false; "Canada,Newfoundland" ; { "collections/61493" ; "Newfoundland" }; { }; { "2246711" ; "Newfoundland" }; { }; { }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1945; " 1945"; true ; true ; true ; true ; false ; false; "Canada,Newfoundland" ; { "collections/61492" ; "Newfoundland" }; { }; { "2246699" ; "Newfoundland" }; { }; { }; }; -- V4.2 Gender = true -- V4.7 Birth = true
};
["Australia"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1828; " 1 Nov 1828"; false; true ; true ; false ; false ; false; "Australia,NSW" ; { "collections/1186" ; "NSW" }; { "new+south+wales%2c+1828+census+householders'+returns" ; "NSW" }; { "2177300" ; "NSW" }; { }; { }; };
{ 1841; " 2 Mar 1841"; false; true ; false; false ; false ; false; "Australia,NSW" ; { "collections/1214" ; "NSW" }; { "new+south+wales+1841+census" ; "NSW" }; { "2317857" ; "NSW" }; { }; { }; };
{ 1841; " 1841"; false; true ; true ; false ; true ; false; "Australia,SA" ; { }; { "south+australia+census" ; "SA" }; { }; { }; { }; };
{ 1881; " 2 Mar 1881"; false; true ; true ; false ; false ; false; "Australia,NT" ; { }; { "northern-territory-census" ; "NT" }; { }; { }; { }; };
{ 1891; " 5 Apr 1891"; false; true ; false; false ; false ; false; "Australia,NSW" ; { "collections/1733" ;"NSW (free index)" }; { "new+south+wales+1891+census" ; "NSW" }; { "2317858" ; "NSW" }; { }; { }; };
{ 1901; "31 Mar 1901"; false; true ; false; false ; false ; false; "Australia,NSW" ; { "collections/1738" ;"NSW (free index)" }; { "new+south+wales+1901+census" ; "NSW" }; { }; { }; { }; };
{ 1911; " 2 Apr 1911"; false; true ; true ; false ; false ; false; "Australia,NT" ; { }; { "northern-territory-census" ; "NT" }; { }; { }; { }; };
{ 1921; " 2 Apr 1921"; false; true ; true ; false ; false ; false; "Australia,NT" ; { }; { "northern-territory-census" ; "NT" }; { }; { }; { }; };
};
["Germany"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1819; " Aug 1819"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1867; " 3 Dec 1867"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1890; " 1 Dec 1890"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1900; " 1 Dec 1900"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1905; " 1 Dec 1905"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1910; " 1 Dec 1910"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1916; " 5 Dec 1916"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1917; " 5 Dec 1917"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1919; " 8 Oct 1919"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1925; "16 Jun 1925"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1933; "16 Jun 1933"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1939; "17 May 1939"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
};
["Denmark"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1787; " 1 Jul 1787"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61724" ; "(free)" }; { }; { }; { "10685/1787-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1801; " 1 Feb 1801"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61726" ; "(free)" }; { }; { }; { "10684/1801-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1834; " 1 Feb 1834"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61725" ; "(free)" }; { }; { "2771431" }; { "10681/1834-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1840; " 1 Feb 1840"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61727" ; "(free)" }; { }; { "2778651" }; { "10680/1840-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1845; " 1 Feb 1845"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61735" ; "(free)" }; { }; { "2778652" }; { "10673/1845-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1850; " 1 Feb 1850"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61734" ; "(free)" }; { }; { "2778653" }; { "10193/1850-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1855; " 1 Feb 1855"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61737" ; "(free)" }; { }; { "2778654" }; { "10258/1855-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1860; " 1 Feb 1860"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61736" ; "(free)" }; { }; { "2778655" }; { "10194/1860-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1870; " 1 Feb 1870"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61733" }; { }; { "2778656" }; { "10195/1870-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1880; " 1 Feb 1880"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61729" ; "(free)" }; { }; { "2778657" }; { "10189/1880-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1890; " 1 Feb 1890"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61732" ; "(free)" }; { }; { "2800928" }; { "10188/1890-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1901; " 1 Feb 1901"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61731" ; "(free)" }; { }; { "2800929" }; { "10187/1901-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1906; " 1 Feb 1906"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2800931" }; { "10253/1906-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1911; " 1 Feb 1911"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2691890" }; { "10184/1911-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1916; " 1 Feb 1916"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2717471" }; { "10196/1916-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1921; " 1 Feb 1921"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2721601" }; { "10197/1921-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1925; " 5 Nov 1925"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2718007" }; { "10190/1925-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1930; " 5 Nov 1930"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2800932" }; { "10181/1930-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1940; " 5 Nov 1940"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { }; { "10706/1940-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1950; " 5 Nov 1950"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { }; { }; { }; }; -- V4.7 -- V4.9
};
["Norway"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1801; " 1 Feb 1801"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/61749" }; { }; { "3733603" }; { "10814/1801-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1865; "31 Dec 1865"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/61753" }; { }; { "3756102" }; { "10807/1865-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1870; "31 Dec 1870"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/62015" }; { }; { "4135961" }; { "10937/1870-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1875; "31 Dec 1875"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/61751" }; { }; { "1529106" }; { "10936/1875-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1891; " 1 Jan 1891"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/60603" }; { }; { "4067726" }; { "10730/1891-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1900; " 3 Dec 1900"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/60604" }; { }; { "3744863" }; { "10731/1900-norway-census" }; { }; }; -- V4.7 -- V4.9 or 2 Dec 1900
{ 1910; " 1 Dec 1910"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/60605" }; { }; { }; { "10732/1910-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1920; " 1 Dec 1920"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { }; { }; { }; { }; { }; }; -- V4.7 -- V4.9
};
}
for strRegion, tblRegion in pairs (tblLookup) do -- Find matching country entry for profile
if strCensusList:match(strRegion) then
for intRow, tblRow in ipairs (tblRegion) do -- Assign each row of data
local intRow = #tblProfile + 1 -- V4.1
tblProfile[intRow] = { Polygamous=dicUserOption.Polygamous }-- V4.3
local dicProfile = tblProfile[intRow]
local isNoRecord = true -- V4.3 -- Assume no online search
for intCol, anyCol in ipairs (tblRow) do -- Assign each column value
local strCol = tblColumn[intCol]
if strCol:match("^%u%l+$") -- V4.3 -- Unconditional column name
or #dicLookupSite[strCol] > 1 then -- V4.3 -- Web site conditional column
if type(anyCol) == "table" then -- V4.2 -- Adjust website Record & Locale tables
local strRecord = anyCol[1] or " "
local strLocale = anyCol[2] or " "
if #strRecord > 2 then isNoRecord = false end -- V4.3 -- Check for online search
if #strLocale > 1 then
strLocale = "
"..strLocale -- Locale goes below Census Date using
strLocale = strLocale:gsub(" "," ") -- V4.3 -- Force onto one line with no-break spaces
end
if strCol == "Fam_Search" then -- V4.6
strRecord = strRecord:gsub("(%d+) ?","&f.collectionId=%1")
end
anyCol = { Record = strRecord; Locale = strLocale; }
end
dicProfile[strCol] = anyCol
end
end
local datCensus = fhNewDate()
datCensus:SetValueAsText(dicProfile.When) -- Convert each When string to FH Simple Date Object
dicProfile.Date = datCensus:Clone() -- Save cloned FH Simple Date Object
dicProfile.When = dicProfile.When:gsub(" "," ") -- Force no-break spaces into When to allow sorting
if ( dicUserOption.PartCensus and dicProfile.Part ) -- V4.1 -- Remove partial Census records
or ( dicUserOption.MissCensus and isNoRecord ) then -- V4.3 -- Remove any with no online search
table.remove(tblProfile)
end
end
break
end
end
return tblProfile
end -- function TblCensusProfile
function StrFilteredNameForURI(strName) -- V2.2
strName = strName:gsub('%[%[.-%]%]','') -- Exclude double bracketed text
strName = strName:gsub("'.-'",""):gsub('".-"',"") -- Exclude quoted text
strName = strName:gsub('[%?%*%^"~%%]','') -- Remove '?' & '*' wildcards used by Ancestry & FamilySearch, '^' that upsets FMP, and '"' & '~' & '%' that upset FamilySearch
strName = encoder.StrEncode_URI(strName) -- Encode for URI
strName = strName:gsub('[%+%.]',' ') -- Keep space instead of '+' and '.' in URI for MyHeritage conversion to "%2F3"
return strName
end -- function StrFilteredNameForURI
-- Is Date Available -- -- V4.1
function DateExists(ptrDate)
if ptrDate:IsNull()
or fhGetValueAsDate(ptrDate):IsNull() then return false end -- Date Phrase with no Date
return true
end -- function DateExists
-- Format Family Date for Java Script Sort --
function StrFamilyDate(ptrFamDate)
-- ptrFamDate Marriage or 1st Child Birth Date
local strFamDate = fhGetItemText(ptrFamDate,"~:ABBREV2") -- Formats: "dd mmm yyyy?", "aft yyyy", "bef yyyy", "yyyy-yyyy", "____-yyyy", "yyyy-____"
if strFamDate ~= "" then
strFamDate = strFamDate:gsub("^c%. (.-%d%d%d%d)","%1?")
local intFamDate = string.len(strFamDate:gsub("(%d%d%d%d)[%d%?%-_]*","%1")) -- Ignoring any ? or - or _ after 4 digit year
strFamDate = string.rep(" ",11 - intFamDate)..strFamDate -- 17 May 2014 Pad date with leading spaces to 11 chars long up to 4 digit year
strFamDate = encoder.StrEncode_XML(strFamDate):gsub(" "," ")
end
return strFamDate
end -- function StrFamilyDate
-- Current Family At Census Date Timeline --
function CurrentFamily(ptrIndi,tblProfile,tblDetails) -- 15 Nov 2015 V3.9 completely rewritten
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- tblDetails Details of Individual
if not tblDetails.Family then
tblDetails.Family = {} -- Create Family As Spouse timeline
local ptrFamsTag = fhNewItemPtr()
local ptrFamIndi = fhNewItemPtr()
local ptrFamDate = fhNewItemPtr()
ptrFamsTag:MoveTo(ptrIndi,"~.FAMS") -- First Family As Spouse of Individual
while ptrFamsTag:IsNotNull() do
ptrFamIndi = fhGetValueAsLink(ptrFamsTag)
ptrFamDate:MoveTo(ptrFamIndi,"~.MARR.DATE")
if not DateExists(ptrFamDate) then -- If no Marriage Date use Birth of First Child -- v4.1
ptrFamDate:MoveTo(ptrFamIndi,"~.CHIL>BIRT.DATE")
end
if DateExists(ptrFamDate) then -- V4.1
local strStatus = fhGetItemText(ptrFamIndi,"~._STAT")
if strStatus ~= "Never Married"
and strStatus ~= "Unmarried Couple" then -- Family couple married or with child, and Date defined
for intTag, strTag in ipairs ({ "~.HUSB[1]>"; "~.WIFE[1]>"; "~.HUSB[2]>"; "~.WIFE[2]>"; }) do
local ptrSpouse = fhGetItemPtr(ptrFamIndi,strTag)
if not ptrSpouse:IsSame(ptrIndi) then -- Spouse found, so find surname of a wife's husband
local strHusband = ""
if fhGetItemText(ptrIndi,"~.SEX") == "Female" then
strHusband = fhGetItemText(ptrFamIndi,"~.HUSB>NAME:SURNAME")
end
local intFamily = #tblDetails.Family
if intFamily > 0 then
local tblFamily = tblDetails.Family[intFamily]
if not tblFamily.FamLink
and fhGetValueAsDate(tblFamily.FamDate):Compare(fhGetValueAsDate(ptrFamDate)) > 0 then
table.remove(tblDetails.Family) -- Remove date with previous Spouse that is after current family date
end
end
-- Save current Family as Spouse, Event Date, and Husband Surname for partners together
table.insert(tblDetails.Family,{ FamLink=ptrFamIndi:Clone(); FamDate=ptrFamDate:Clone(); Husband=strHusband; })
local ptrTags = fhNewItemPtr()
local ptrFams = fhNewItemPtr()
local ptrDate = fhNewItemPtr()
ptrTags:MoveTo(ptrSpouse,"~.FAMS") -- First Family As Spouse of Spouse
while ptrTags:IsNotNull() do
ptrFams = fhGetValueAsLink(ptrTags)
if not ptrFams:IsSame(ptrFamIndi) then
ptrDate:MoveTo(ptrFams,"~.MARR.DATE")
if not DateExists(ptrDate) then -- If no Marriage Date use Birth of First Child -- V4.1
ptrDate:MoveTo(ptrFams,"~.CHIL>BIRT.DATE")
end
if DateExists(ptrDate) -- Save date when Spouse with another partner, only after date with original partner -- V4.1
and fhGetValueAsDate(ptrDate):Compare(fhGetValueAsDate(ptrFamDate)) > 0 then
table.insert(tblDetails.Family,{ FamDate=ptrDate:Clone(); })
end
end
ptrTags:MoveNext("SAME_TAG")
end
for intTag, strTag in ipairs ({ "~.DEAT.DATE"; "~.BURI.DATE"; "~.CREM.DATE"; }) do
ptrDate:MoveTo(ptrSpouse,strTag)
if DateExists(ptrDate) then -- Save date of Demise of current partner -- V4.3
if tblProfile.Polygamous and -- Polygamous relationships recognised? -- V4.3
not tblDetails.Family[#tblDetails.Family].FamLink then
table.remove(tblDetails.Family) -- Remove date of Marriage/First Child with another partner -- V4.3
end
table.insert(tblDetails.Family,{ FamDate=ptrDate:Clone(); })
break
end
end
break
end
end
end
end
for intTag, strTag in ipairs ({ "~.DIV.DATE"; "~.DIVF.DATE"; "~.ANUL.DATE"; "~.EVEN-SEPARATION.DATE"; }) do
ptrFamDate:MoveTo(ptrFamIndi,strTag)
if DateExists(ptrFamDate) then -- Save date of Divorce, Divorce Filed, Annulment, or Separation -- V4.1
table.insert(tblDetails.Family,{ FamDate=ptrFamDate:Clone(); })
break
end
end
ptrFamsTag:MoveNext("SAME_TAG")
end
-- Reverse sort the Family As Spouse timeline by Date, so Event Date before Census Date is easy to find below
table.sort(tblDetails.Family,function(fam1,fam2) return fhGetValueAsDate(fam1.FamDate):Compare(fhGetValueAsDate(fam2.FamDate)) > 0 end)
end
local datCensusDate = tblProfile.Date
if tblDetails.CenDate:IsNull() -- V4.2
or tblDetails.CenDate:Compare(datCensusDate) ~= 0 then -- Update details for different Census Date -- V4.2
local ptrFamLink = fhNewItemPtr()
if tblDetails.CenDate:Compare(datCensusDate) == 1 then -- V5.0 -- Details later than census date need resetting
tblDetails.Sname = tblDetails.Sborn
tblDetails.sname = tblDetails.sborn -- V4.2 -- Restore birth Surname = female maiden name
tblDetails.SNAME = tblDetails.SBORN
end
tblDetails.CenDate = datCensusDate -- Save Census Date
tblDetails.FamLink = ptrFamLink -- Clear Family As Spouse and Event Date
tblDetails.FamDate = ""
for intFamily, tblFamily in ipairs (tblDetails.Family) do -- Search Family As Spouse timeline until Event Date before Census Date
if fhGetValueAsDate(tblFamily.FamDate):Compare(datCensusDate) <= 0 then
ptrFamLink = tblFamily.FamLink
if ptrFamLink then -- Family As Spouse link, so set Current Family details
local strHusband = tblFamily.Husband
if strHusband ~= "" then -- Married women use husband's surname
tblDetails.SNAME = strHusband -- V4.9
tblDetails.Sname = StrFilteredNameForURI(strHusband)
tblDetails.sname = encoder.StrEncode_XML(strHusband)
end
tblDetails.FamLink = ptrFamLink:Clone() -- Family As Spouse link and Date (Marriage or 1st Born)
tblDetails.FamDate = StrFamilyDate(tblFamily.FamDate)
end
break -- Event Date without Family link leaves Family details blank (Divorce, Annulment, partner Marries another, etc)
end
end
end
end -- function CurrentFamily
-- Get Individual Name & Birth & Death Details --
TblDetails = {} -- Save details in table cache for quick subsequent lookup
function TblIndividualDetails(ptrIndi,tblProfile,intRecId)
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- intRecId Individual Record Id
intRecId = intRecId or fhGetRecordId(ptrIndi)
local tblDetails = TblDetails[intRecId]
if not tblDetails then
TblDetails[intRecId] = {}
tblDetails = TblDetails[intRecId]
local strGiven = fhGetItemText(ptrIndi,"~.NAME:GIVEN_ALL")
local strSname = fhGetItemText(ptrIndi,"~.NAME:SURNAME")
local strPlace = fhGetItemText(ptrIndi,"~.BIRT.PLAC")
tblDetails.RecId = intRecId
tblDetails.Dates = fhCallBuiltInFunction("LifeDates",ptrIndi):gsub("c%. (.-%d%d%d%d)","%1?"):gsub(" "," ")
tblDetails.GIVEN = strGiven -- V4.9
tblDetails.Given = StrFilteredNameForURI(strGiven) -- V4.2
tblDetails.given = encoder.StrEncode_XML(strGiven)
tblDetails.SBORN = strSname -- V4.9
tblDetails.Sborn = StrFilteredNameForURI(strSname) -- V4.2
tblDetails.sborn = encoder.StrEncode_XML(strSname) -- V4.2
tblDetails.SNAME = strSname -- V4.9
tblDetails.Sname = tblDetails.Sborn -- V4.2
tblDetails.sname = tblDetails.sborn -- V4.2
tblDetails.Place = StrFilteredNameForURI(strPlace) -- V4.2
tblDetails.Death = general.EstimatedDeathDates(ptrIndi,9).Max -- V4.3 -- Fix erroneous EstimatedDeathDate function
local dicDate = general.EstimatedBirthDates(ptrIndi,9) -- V4.1 -- Fix erroneous EstimatedBirthDate function
local yearMin = dicDate.Min:GetYear()
local yearMax = math.min(dicDate.Max:GetYear(),IntYearToday) -- Max year is limited to current year -- V4.2
local intYear = dicDate.Mid:GetYear() -- V4.2
local intSpan = 0
local strYear = "" -- Birth date Year & Span for each web site -- V4.2
local strSpan = "" -- More efficient here once, than every time needed in a lookup
if intYear > 0 then -- Estimated Birth year available
intSpan = math.ceil ( ( yearMax - yearMin ) / 2 ) -- Tolerance span = rounded up ( max year - min year ) / 2
if intSpan < 40 then -- Round up even exact Dates to ±5
if intSpan <= 5 then
yearMin = intYear - 5 -- Extend Fam_Search range
yearMax = intYear + 5
end
intSpan = math.ceil( math.max(intSpan,5) / 5) * 5 -- Round up low Span to ±5, ±10, ±20, ±40
if intSpan == 15 then intSpan = 20 -- Round up 15 to 20
elseif intSpan > 20 and intSpan < 40 then intSpan = 40 end -- Round up 21-39 to 40
end
strYear = tostring(intYear or "") -- Fact date Year & Span for each web site -- V4.2
strSpan = tostring(intSpan or "") -- More efficient here once, than every time needed in a lookup
end
tblDetails.Birth = intYear
tblDetails.Range = strSpan
tblDetails.Year = {}
tblDetails.Span = {}
tblDetails.Year.AncestryCo = strYear
tblDetails.Span.AncestryCo = tostring(math.min(intSpan,10)) -- Exact ±10 may give greater or fewer results than Broad!
tblDetails.Year.FindMyPast = strYear
tblDetails.Span.FindMyPast = strSpan
tblDetails.Year.Fam_Search = tostring(yearMin)
tblDetails.Span.Fam_Search = tostring(yearMax)
tblDetails.Year.MyHeritage = strYear
tblDetails.Span.MyHeritage = strSpan
tblDetails.Year.Canada_Gov = tostring(yearMin) -- V5.2
tblDetails.Span.Canada_Gov = tostring(yearMax)
local strSex = fhGetItemText(ptrIndi,"~.SEX") -- V4.2 -- Sex format for each web site
tblDetails.Sex = {}
tblDetails.Sex.AncestryCo = strSex:lower():match("^([fm])") -- V4.7
tblDetails.Sex.FindMyPast = strSex
tblDetails.Sex.Fam_Search = strSex:lower() -- V4.6
tblDetails.Sex.MyHeritage = strSex:match("^([FM])")
local dicSex = {Male=1;Female=2;Unknown=8;}
tblDetails.Sex.Canada_Gov = ({Male=1;Female=2;Unknown=8;})[strSex] -- V5.2
tblDetails.CenDate = fhNewDate() -- V4.2
end
if tblProfile then -- Use mid age based on mid year rather than max age -- V4.2
tblDetails.Age = tblProfile.Year - tblDetails.Birth
CurrentFamily(ptrIndi,tblProfile,tblDetails) -- Family at Census details
else
tblDetails.Age = 0
tblDetails.SNAME = tblDetails.SBORN
tblDetails.Sname = tblDetails.Sborn -- Restore birth Surname = female maiden name for Father & Mother column -- V4.2
tblDetails.sname = tblDetails.sborn
tblDetails.CenDate = fhNewDate() -- 15 Nov 2015 V3.9 -- V4.2
end
return TblDetails[intRecId]
end -- function TblIndividualDetails
-- Check Death Date if Relation alive and Name exists -- -- 15 Nov 2015 V3.9 -- V4.2
function IfRelationExists(tblRelation,tblProfile)
-- tblRelation tblDetails of related Individual
-- tblProfile Census Profile of Country
local isName = ( #tblRelation.Given > 0 or #tblRelation.Sname > 0 ) -- Does their Name exist -- V4.2
local datDeathDate = fhNewDate()
datDeathDate:SetSimpleDate(tblRelation.Death)
return ( datDeathDate:Compare(tblProfile.Date) >= 0 and isName ) -- Is alive at Census Date and Name exists?
end -- function IfRelationExists
-- Get the Spouse if married or the Parents if unmarried and young at time of Census --
function StrRelatives(ptrIndi,tblProfile,ptrFamily,intAge,strSpouseTemplate,strMotherTemplate,strFatherTemplate)
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- ptrFamily Link to Family
-- intAge Age of Individual
-- str***Template Relative Web Template
local strRelatives = ""
local strRelation = ""
local tblRelation = {}
local ptrRelation = fhNewItemPtr()
if strSpouseTemplate and ptrFamily:IsNotNull() then -- Get Spouse at time of Census
if fhGetItemText(ptrIndi,"~.SEX") == "Female" then
ptrRelation:MoveTo(ptrFamily,"~.HUSB>")
else
ptrRelation:MoveTo(ptrFamily,"~.WIFE>")
end
if ptrRelation:IsNotNull() then
tblRelation = TblIndividualDetails(ptrRelation,tblProfile)
if IfRelationExists(tblRelation,tblProfile) then -- Spouse is alive at current Census Date with a Name -- 15 Nov 2015 V3.9 -- V4.2
strRelation = strSpouseTemplate
strRelation = strRelation:replace("{GivenNames}",tblRelation.Given)
strRelation = strRelation:replace("{TheSurname}",tblRelation.Sname) -- 13 Apr 2014 V3.2 use wife's last husband's surname
strRelatives = strRelatives..strRelation
end
end
end
if not intAge -- Unconditionally include Parents (FamilySearch and MyHeritage) -- V4.2
or ( strRelatives == "" and intAge <= 25 ) then -- Only include Parents of unmarried young Individuals
-- Does NOT cope with adoptive/foster Parents !!!!
ptrRelation:MoveTo(ptrIndi,"~.~MOTH>") -- Get Mother
if strMotherTemplate and ptrRelation:IsNotNull() then
tblRelation = TblIndividualDetails(ptrRelation,tblProfile)
if IfRelationExists(tblRelation,tblProfile) then -- Mother is alive at current Census Date with a Name -- 15 Nov 2015 V3.9 -- V4.2
strRelation = strMotherTemplate
strRelation = strRelation:replace("{GivenNames}",tblRelation.Given)
strRelation = strRelation:replace("{TheSurname}",tblRelation.Sname)
strRelation = strRelation:replace("{MaidenName}",tblRelation.Sborn) -- V4.2
strRelatives = strRelatives..strRelation
end
end
ptrRelation:MoveTo(ptrIndi,"~.~FATH>") -- Get Father
if strFatherTemplate and ptrRelation:IsNotNull() then
tblRelation = TblIndividualDetails(ptrRelation)
if IfRelationExists(tblRelation,tblProfile) then -- Father is alive at current Census Date with a Name -- 15 Nov 2015 V3.9 -- V4.2
strRelation = strFatherTemplate
strRelation = strRelation:replace("{GivenNames}",tblRelation.Given)
strRelation = strRelation:replace("{TheSurname}",tblRelation.Sname)
strRelatives = strRelatives..strRelation
end
end
end
return strRelatives
end -- function StrRelatives
-- Build Web Site Lookup -- V4.2
function StrBuildLookup(strWebAddress,tblProfile,strWebSite,ptrIndi,tblDetails)
-- strWebAddress Web Site Domain Name
-- tblProfile Census Profile of Country
-- strWebSite Index to Census and Details data
-- ptrIndi Individual of Interest
-- tblDetails Details of Individual
local strTemplate = ""
if #strWebAddress > 1 then -- Include chosen website
if not ptrIndi then -- Build table heading
strTemplate = StrWebHeadTemplate:replace("{WebAddress}",strWebAddress)
elseif #tblProfile[strWebSite].Record < 2 then -- V4.3 -- Build missing Census year entry
strTemplate = StrMissingTemplate
strTemplate = strTemplate:replace("{CensusLink}",tblProfile.When)
strTemplate = strTemplate:replace("{LocaleLink}",tblProfile[strWebSite].Locale)
else -- Build table data lookup anchor tag
strWebAddress = ({ en="eng/home/result"; fr="fra/accueil/resultat"; xx=strWebAddress; })[strWebAddress:match("canada.ca/(.-)$") or "xx"] -- V5.2
strTemplate = TblTemplate[strWebSite].Lookup
strTemplate = strTemplate:replace("{WebAddress}",strWebAddress)
strTemplate = strTemplate:replace("{RecordCode}",(tblProfile[strWebSite].Record:gsub("^ ",""))) -- V5.2
strTemplate = strTemplate:replace("{GivenNames}",tblDetails.Given) -- V4.1 Use all Given names instead of tblDetails.First
strTemplate = strTemplate:replace("{TheSurname}",tblDetails.Sname)
local strBirth = "" -- Birth Year details
if tblProfile.Birth and tblDetails.Birth > 0 then -- Birth date in Census and Event -- V4.2
strBirth = TblTemplate[strWebSite].Birth -- Set the Year and Span values for web site template
strBirth = strBirth:replace("{Birth_Year}",tblDetails.Year[strWebSite])
strBirth = strBirth:replace("{Birth_Span}",tblDetails.Span[strWebSite])
end
strTemplate = strTemplate:replace("{Birth_Data}",strBirth)
strTemplate = strTemplate:replace("{BirthPlace}",tblDetails.Place)
local strGender = "" -- Gender Sex details
if tblProfile.Gender then -- Gender data in Census -- V4.2
local strSex = tblDetails.Sex[strWebSite]
if strSex then -- Gender Sex value for web site template
strGender = TblTemplate[strWebSite].Gender
strGender = strGender:replace("{Gender_Sex}",strSex)
end
end
strTemplate = strTemplate:replace("{Gender_Sex}",strGender)
local intAge = nil -- Spouse, Father, Mother details
if strWebSite == "AncestryCo"
or strWebSite == "FindMyPast" then
intAge = tblDetails.Age -- Mid age for AncestryCo and FindMyPast
end
local strSpouse = TblTemplate[strWebSite].Spouse -- Relatives templates
local strMother = TblTemplate[strWebSite].Mother
local strFather = TblTemplate[strWebSite].Father
if strWebSite == "FindMyPast" then -- Special rules for FindMyPast
strSpouse = nil
strMother = nil
strFather = nil
if tblProfile.Other then
local strOther = TblTemplate[strWebSite].Other
strSpouse = strOther
strMother = strOther
strFather = strOther
end
if tblProfile.Spouse then
strSpouse = TblTemplate[strWebSite].Spouse
end
end
local strRelatives = StrRelatives(ptrIndi,tblProfile,tblDetails.FamLink,intAge,strSpouse,strMother,strFather)
strTemplate = strTemplate:replace("{Relatives}",strRelatives)
if strWebSite == "MyHeritage" then -- Needs "%2F3" space substitute only within Names
strTemplate = strTemplate:replace(" ","%2F3") -- U+02F3 Modifier Letter Low Ring space sub in Names
end
strTemplate = StrWebSiteTemplate:replace("{WebSiteCol}",strTemplate)
strTemplate = strTemplate:replace("{CensusLink}",tblProfile.When)
strTemplate = strTemplate:replace("{LocaleLink}",tblProfile[strWebSite].Locale)
end
end
return strTemplate
end -- function StrBuildLookup
-- Iterate List of Records to Search -- -- V4.3 -- Avoids slow "IsAncestor/Descendant/RelativeOf" functions
function TblRecordList(tblRecordList,strRecordList)
-- tblRecordList Individual Records List
-- strRecordList Relatives Record selection mode
if strRecordList then -- Ancestors or Descendants or Relations needed
local arrRelative = { } -- Array of Individual Record Id & Pointers
local dicRelative = { } -- Dictionary of processed Individual Record Id
local dicFamilies = { } -- Dictionary of processed Family Record Id
local function newRelative(ptrRec) -- Add new Relative to list
if ptrRec:IsNotNull() then
local intRec = fhGetRecordId(ptrRec)
if not dicRelative[intRec] then
dicRelative[intRec] = true
table.insert(arrRelative,{ Id=intRec; Rec=ptrRec:Clone(); })
if #arrRelative % 1000 == 0 then fhSleep(10,7) end -- Prevent FH Stopped Working/Not Responding message
return true
end
end
return false
end -- local function newRelative
local function getAncestor(ptrRec) -- Get next Ancestor generation
if newRelative(ptrRec) then
local ptrFamc = fhGetItemPtr(ptrRec,"~.FAMC") -- Cater for multiple parent families
while ptrFamc:IsNotNull() do
local ptrFam = fhGetValueAsLink(ptrFamc)
getAncestor(fhGetItemPtr(ptrFam,"~.HUSB[1]>")) -- Cater for both sex & same sex parents
getAncestor(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
getAncestor(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
getAncestor(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
ptrFamc:MoveNext("SAME_TAG")
end
end
end -- local function getAncestor
local function getDescends(ptrRec) -- Get next Descendant generation
if newRelative(ptrRec) then
local ptrFams = fhGetItemPtr(ptrRec,"~.FAMS") -- Cater for multiple spouse families
while ptrFams:IsNotNull() do
local ptrFam = fhGetValueAsLink(ptrFams)
getDescends(fhGetItemPtr(ptrFam,"~.HUSB[1]>")) -- Cater for both sex & same sex partners
getDescends(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
getDescends(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
getDescends(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
local ptrChil = fhGetItemPtr(ptrFam,"~.CHIL") -- Get all children
while ptrChil:IsNotNull() do
getDescends(fhGetValueAsLink(ptrChil))
ptrChil:MoveNext("SAME_TAG")
end
ptrFams:MoveNext("SAME_TAG")
end
end
end -- local function getDescends
local function getAnc_Desc(ptrRec) -- Get both Ancestors & Descendants
getAncestor(ptrRec)
dicRelative[fhGetRecordId(ptrRec)] = false -- Remove root Rec Id to allow Descendants
table.remove(arrRelative,1)
getDescends(ptrRec)
end -- local function getAnc_Desc
local function getRelation(ptrRec) -- Get next all Relations generation
if newRelative(ptrRec) then
for _, strFamx in ipairs ({ "~.FAMC"; "~.FAMS"; }) do -- Cater for all parent/spouse families
local ptrFamx = fhGetItemPtr(ptrRec,strFamx)
while ptrFamx:IsNotNull() do
local ptrFam = fhGetValueAsLink(ptrFamx)
local intFam = fhGetRecordId(ptrFam)
if not dicFamilies[intFam] then -- New family Record Id
dicFamilies[intFam] = true
getRelation(fhGetItemPtr(ptrFam,"~.HUSB[1]>")) -- Cater for both sex & same sex partners
getRelation(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
getRelation(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
getRelation(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
local ptrChil = fhGetItemPtr(ptrFam,"~.CHIL") -- Get all children
while ptrChil:IsNotNull() do
getRelation(fhGetValueAsLink(ptrChil))
ptrChil:MoveNext("SAME_TAG")
end
end
ptrFamx:MoveNext("SAME_TAG")
end
end
end
end -- local function getRelation
local ptrRoot = tblRecordList[1]
local isAncestor = strRecordList:match("Ancestor") -- Ancestors or Ancestors & Descendants ?
local isDescends = strRecordList:match("Descendant") -- Descendants or Ancestors & Descendants ?
local isRelation = strRecordList:match("Relation") -- Relations ? i.e. all in same Relation Pool
if isAncestor
and isDescends then getAnc_Desc(ptrRoot)
elseif isAncestor then getAncestor(ptrRoot)
elseif isDescends then getDescends(ptrRoot)
elseif isRelation then getRelation(ptrRoot)
end
table.sort(arrRelative,function(arrA,arrB) return arrA.Id < arrB.Id end)
for intIndi = 1, #arrRelative do
tblRecordList[intIndi] = arrRelative[intIndi].Rec -- Create simple Record list
end
end
progbar.Setup(iup_gui.DialogueAttributes("Bars"))
local intStep = 0
local intIndi = #tblRecordList -- Iterate all from Select Records dialogue
if intIndi >= 500 then
progbar.Start("Searching "..intIndi.." Individuals",intIndi) -- Optionally start the Progress Bar -- V4.3
end
intIndi = 0
return function () -- Iterator function
intStep = intStep + 1
intIndi = intIndi + 1
local ptrIndi = tblRecordList[intIndi]
if intStep > 29 and ptrIndi then
progbar.Message("Record Id "..fhGetRecordId(ptrIndi)) -- Report progress occaisionally
progbar.Step(intStep)
intStep = 0
fhSleep(10,7) -- Prevent FH Stopped Working message
end
if progbar.Stop() then return end
return ptrIndi
end
end -- function TblRecordList
-- Check if Census is missing and Individual was in Census Locality on Census Date
-- Many U.K. & Australia/Canada Census Dates were in same Year, some identical Dates and some different Dates, so must cater for all cases
TblFacts =
{
BIRT=true; BAPM=true; CHR =true; -- V4.3 -- Key Facts to save regardless of Date format
MARR=true; EMIG=true; IMMI=true;
DEAT=true; BURI=true; CREM=true;
Cache={ }; -- V4.3 -- Save the Facts in cache for quick subsequent lookup
}
function GetItemPlace(ptrFact,strTag)
return fhGetItemText(ptrFact,strTag):gsub('".-"',''):lower() -- Exclude "string quoted" place names -- V4.5
end -- function GetItemPlace
function CacheFact(ptrFact,isInLocality) -- Save CENSus and other Facts in the Facts Cache -- V4.8 -- V4.9
local strTag = fhGetTag(ptrFact)
local datDate = fhGetValueAsDate(fhGetItemPtr(ptrFact,"~.DATE"))
if not datDate:IsNull() then -- V4.3 -- Omit any Facts without a Date
local strPlace = GetItemPlace(ptrFact,"~.PLAC") -- V4.0 -- Place case-insensitive match -- V4.5
local strPlac2 = GetItemPlace(ptrFact,"~._PLAC") -- V4.3 -- EMIG/IMMIgration other place -- V4.5
if strTag == "CENS"
or isInLocality and #(strPlace..strPlac2) > 1 and -- Facts for Home/Abroad mode enabled, and Fact has a Place
( TblFacts[strTag] == true or -- V4.3 -- Any key Fact
( datDate:GetType() == "Simple" and -- Any Fact with simple Date, and
datDate:GetSubtype() == "" and -- without a Qualifier, and
#(datDate:GetDisplayText("COMPACT")) > 9 ) -- format is "dd mmm yyyy" with all parts needed, so length is > 9
) then
local intDpt = datDate:GetDatePt1() -- V4.3 -- Obtain Date Point
table.insert(TblFacts.Cache,{ Tag=strTag; Date=datDate:Clone(); Dpt=intDpt; Place=", "..strPlace..", "; Plac2=", "..strPlac2..", "; })
end
end
end -- function CacheFact
function SaveFacts(ptrRec,isInLocality)
for ptrFact in iterate.Facts(ptrRec) do -- Save CENSus and optionally key Facts & all Facts with both full Simple Date and Place fields
CacheFact(ptrFact,isInLocality) -- V4.9
end
local intFact = 1
local ptrFact = fhGetItemPtr(ptrRec,"~.~SHAR>")
while ptrFact:IsNotNull() do -- V4.8 -- Include witnessed facts %INDI.~SHAR[i]>%
CacheFact(ptrFact,isInLocality) -- V4.9
intFact = intFact + 1
ptrFact = fhGetItemPtr(ptrRec,"~.~SHAR["..intFact.."]>")
end
end -- function SaveFacts
function CensusMissing(ptrIndi,tblProfile,intRecId,dicUserOption)
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- intRecId Individual Record Id
-- dicUserOption User optional mode settings -- V4.3
-- InLocality Facts for Home/Abroad mode
-- EachCensus Min Age in All Census mode
-- MinimumAge Minimum Age at Census
local isInLocality = dicUserOption.InLocality -- V4.3 -- Facts for Home/Abroad optional mode setting
local isInLocale = true -- Assume person is in Census Locality on Census Date and missing Census Event
local arrLocale = tblProfile.Locale:lower():plain():split() -- V4.0 case-insensitive match
if TblFacts.RecId ~= intRecId then -- Start empty table for each new Record Id
TblFacts.Cache = {}
TblFacts.RecId = intRecId
for intFam, tblFam in ipairs (TblIndividualDetails(ptrIndi,tblProfile,intRecId).Family) do
local ptrFam = tblFam.FamLink -- Save Family CENSus Events, key Facts & all Facts with both full Simple Date and Place fields
if ptrFam and ptrFam:IsNotNull() then SaveFacts(ptrFam,isInLocality) end
end
SaveFacts(ptrIndi,isInLocality) -- Save Individual CENSus Events, key Facts & all Facts with both full Simple Date and Place fields
table.sort( TblFacts.Cache,
function(tblFact1,tblFact2)
local intCompare = tblFact1.Dpt:Compare(tblFact2.Dpt) -- V4.3 -- Sort the Facts by Date Point and if equal ensure CENS first (copes with abnormal dates)
if intCompare == 0 then
return tblFact1.Tag == "CENS" and tblFact2.Tag ~= "CENS" -- V4.3 -- tblFact1.Tag == "CENS" alone fails, as sometimes both tags = "CENS"
end
return intCompare < 0
end
)
end
for intFact, tblFact in ipairs(TblFacts.Cache) do -- Check each Fact in lookup table cache
local strTag = tblFact.Tag
local strPlace = tblFact.Place
local intCompare = tblFact.Date:Compare(tblProfile.Date) -- Comparison = 0 even if only partial match for CENSus Event Year or Month & Year
if intCompare == 0 then -- Fact Date matches Census Date
if strTag == "CENS" and strPlace == ", , " then -- CENSus Event for Date but no Place field
return false -- Assume it is for Census Locality, so Census is NOT missing
end
isInLocale = false -- Assume person is NOT in Census Locality on Census Date and Census is NOT missing
for _, strLocale in ipairs ( arrLocale ) do -- V4.0
if strPlace:match(", -"..strLocale.." -, -") then -- V4.0 -- Space tolerant match for Census locale
if strTag == "CENS" then return false end -- CENSus Event for Date & Locality, so Census is NOT missing
isInLocale = true -- Another Fact for Date & Locality, so Census is missing
break
end
end
elseif isInLocality then -- V4.3 -- Check all cached facts (Test against John Smith & Julia Fish, et al)
if ( intCompare < 0 and strTag == "EMIG" )
or ( intCompare > 0 and strTag == "IMMI" ) then -- Adjust at home place name for EMIGration and IMMIgration events
strPlace = tblFact.Plac2
end
isInLocale = false -- Assume person is NOT in Census Locality on Census Date and Census is NOT missing
for _, strLocale in ipairs ( arrLocale ) do
if strPlace:match(", -"..strLocale.." -, -") then -- Space tolerant match for EMIG/IMMI to Locality before Census, or EMIG/IMMI from Locality after Census, or other Fact in Locality
isInLocale = true -- So person is at home and Census may be missing
break
end
end
if intCompare > 0 then break end -- V4.3 -- Fact Date is after Census Date, so skip later facts
end
end
if dicUserOption.EachCensus -- Apply the Minimum Age threshold unconditionally
or not tblProfile.Child then -- or if no Children are in current Census
local tblDetails = TblIndividualDetails(ptrIndi,tblProfile,intRecId)
if tblDetails.Age < dicUserOption.MinimumAge then return false end -- If younger than Minimum Age then Census is NOT missing
end
return isInLocale -- If in Locality on Date then Census is missing, otherwise Census is NOT missing
end -- function CensusMissing
-- Delete files accessed more than 1 hour ago --
function DeleteOldFiles(strFolder,strFile)
-- strFolder Folder of Files
-- strFile Filename to Delete
local intTime = os.time() - 3600 -- Time in seconds one hour ago
for strEntry in lfs.dir(strFolder) do -- Search for files in the folder
if strEntry ~= "." and strEntry ~= ".." then
local strPath = strFolder..strEntry
local tblAttr, strError = lfs.attributes(strPath) -- Obtain file attributes
if not tblAttr then tblAttr = { mode="attrfail"; error=strError; } end
if tblAttr.mode == "file"
and strEntry:matches(strFile) then -- Simple file with matching name?
if tblAttr.access < intTime then
general.DeleteFile(strPath) -- Delete if access time is an hour ago
end
end
end
end
end -- function DeleteOldFiles
-- Remove [brackets] and turn punctuation into spaces before splitting to count components -- V4.1
function StrShortName(strName)
if #(strName:gsub("%[.-%]",""):gsub("[!-/:-@%[\\%]^_`{|}~]"," "):split(" ")) <= 1 then return "" end
return strName
end -- function StrShortName
local tblFormatName = {}
-- Format Individual Name and Record ID --
function StrFormatName(ptrIndi,strRef,tblProfile,intRecId,isShortNames)
-- ptrIndi Individual of Interest
-- strRef Data Ref of Relative
-- tblProfile Census Profile of Country
-- intRecId Record Id optional
-- isShortNames Chosen Fewer than 2 Name components mode -- V4.1
ptrIndi = fhGetItemPtr(ptrIndi,strRef)
if ptrIndi:IsNull() then return "", "", ptrIndi end -- V4.9
local tblDetails = TblIndividualDetails(ptrIndi,tblProfile,intRecId) -- Obtain name at Census Date if supplied, otherwise birth name
local strNAME = tblDetails.SNAME..", "..tblDetails.GIVEN.." ["..tblDetails.RecId.."]" -- V4.9
local strName = tblDetails.sname..", "..tblDetails.given.." ["..tblDetails.RecId.."]"
if isShortNames then -- if name too short then reduce to empty string and remember -- V4.1
tblFormatName[strName] = tblFormatName[strName] or StrShortName(strName)
strName = tblFormatName[strName]
end
return strName, strNAME, ptrIndi -- V4.9
end -- function StrFormatName
-- Run Census Lookup --
function RunLookup(strCensusList,dicLookupSite,strRecordList,dicUserOption)
-- strCensusList Chosen set of Census entries
-- dicLookupSite Chosen lookup web site details -- V4.3
-- strRecordList Chosen Record selection mode
-- dicUserOption Chosen user optional settings :- -- V4.3
-- PartCensus Chosen Partial Census Records mode -- V4.1
-- MissCensus Chosen Missing Census Records mode -- V4.3
-- ShortNames Chosen Fewer than 2 Name components mode -- V4.1
-- InLocality Chosen Facts for Home/Abroad mode
-- MinimumAge Chosen Minimum Age at Census
-- EachCensus Chosen Min Age in All Census mode
-- Polygamous Chosen Polygamous relationships mode -- V4.3
local strFolder = iup_gui.MachinePath.."\\" -- Define the folder for the browser files
local strPlugin = iup_gui.Plugin:lower():gsub(" ","") -- Define lowercase and despaced Plugin name
local strFile_js = strPlugin..".js" -- Define filenames for .css, .js & .html files
local strFile_css = strPlugin..".css"
local strFile_html = strPlugin..os.tmpname():gsub("^.*\\","-")..".html"-- Lua 5.1 and Lua 5.3 compatible -- V4.5
DeleteOldFiles(strFolder,strPlugin) -- Delete any old copies of the browser files
local putWebPage = general.OpenFile(strFolder..strFile_html,"wb") -- Web page output HTML file -- V4.3
local strWebPage = StrWebPageTemplate -- Web page of headings and placeholders for HTML components
local strWebLine = "" -- Web line in HTML grid
local intWebLine = 0 -- Web line number -- V4.3
local strLineColour = "" -- Web line colour switch
local tblProfile = TblCensusProfile(strCensusList,dicLookupSite,dicUserOption) -- Table of Census data per year for chosen country -- V4.1
local strTitleName = "selected Individuals" -- Title name for Individuals via Select Records
local ptrRecordRoot = fhNewItemPtr() -- Root person for Relationship in Result Set
local tblRecordList = {} -- List of Individual Records to search
local intRecordList = 20000 -- Allow large numer of Individuals via Select Records
if strRecordList then intRecordList = 1 end -- But only one for Ancestors/Descendants/Relations option
tblRecordList = fhPromptUserForRecordSel("INDI",intRecordList) -- Select Records dialogue
if #tblRecordList == 0 then return end
local ptrRoot = tblRecordList[1]:Clone()
if strRecordList then -- Title details of chosen root person for Ancestors/Descendants/Relations option
strTitleName = strRecordList..fhGetItemText(ptrRoot,"~.NAME").." ["..fhGetRecordId(ptrRoot).."] "..fhCallBuiltInFunction("LifeDates",ptrRoot) -- V4.1
end
ptrRecordRoot = fhCallBuiltInFunction("FileRoot"):Clone() -- Use FileRoot to avoid slow fhCallBuiltInFunction("Relationship/Code"...) functions -- V4.3
if ptrRecordRoot:IsNull() then ptrRecordRoot = ptrRoot:Clone() end
strTitleName = strCensusList.." are missing for "..strTitleName -- Prefix Census List to title
strWebPage = strWebPage:replace("{JS_File}",strFile_js) -- Insert web page components into placeholders
strWebPage = strWebPage:replace("{CSS_File}",strFile_css)
strWebPage = strWebPage:replace("{TitleName}",encoder.StrEncode_XML(strTitleName))
for strLookupName, strWebAddress in pairs (dicLookupSite) do -- V4.3 -- Build heading for each lookup web site
strWebPage = strWebPage:replace("{"..strLookupName.."}",StrBuildLookup(strWebAddress))
end
putWebPage:write(strWebPage) -- Write HTML header -- V4.3
local tblPosition = {} -- Result Set tables -- V4.1
local tblIndiName = {}
local tblIndiItem = {}
local tblRecordId = {}
local tblLifeDate = {}
local tblFactItem = {}
local tblGenApart = {}
local tblRelation = {}
local tblFathName = {}
local tblFathItem = {}
local tblMothName = {}
local tblMothItem = {}
for ptrIndi in TblRecordList(tblRecordList,strRecordList) do -- Check each Individual against each Census -- V4.3
local datBirthDate = fhNewDate()
local datDeathDate = fhNewDate()
local strPosition = ""
local intRecordId = fhGetRecordId(ptrIndi)
local strBirthYear = ""
datBirthDate:SetSimpleDate(general.EstimatedBirthDates(ptrIndi,9).Min) -- V4.1 -- Fix erroneous EstimatedBirthDate function
datDeathDate:SetSimpleDate(general.EstimatedDeathDates(ptrIndi,9).Max) -- V4.3 -- Fix erroneous EstimatedDeathDate function
for intEntry, tblProfile in ipairs(tblProfile) do -- Check each Census against life dates of Individual
local datCensusDate = tblProfile.Date
if datBirthDate:Compare(datCensusDate) <= 0
and datDeathDate:Compare(datCensusDate) >= 0 then -- Individual is alive at selected Census Date
-- Check if Census Event is missing and Individual is in Census Locality on Census Date
if CensusMissing(ptrIndi,tblProfile,intRecordId,dicUserOption) then
local tblDetails = TblIndividualDetails(ptrIndi,tblProfile,intRecordId)
local strIndiName, strIndiNAME = StrFormatName(ptrIndi,"~",tblProfile,intRecordId,dicUserOption.ShortNames) -- V4.9
if #strIndiName > 0 then -- Omit if too few Primary Name components? -- V4.1
local strFathName, strFathNAME, ptrFath = StrFormatName(ptrIndi,"~.~FATH>") -- V4.9
local strMothName, strMothNAME, ptrMoth = StrFormatName(ptrIndi,"~.~MOTH>") -- V4.9
local intBirthYear = tblDetails.Birth -- Census is Missing and Individual alive in Census Locality
if intBirthYear > 0 then -- V4.2
strBirthYear = encoder.StrUTF8_XML(tostring(intBirthYear)..encoder.StrANSI_UTF8(" ± ")..tblDetails.Range)
end
if strLineColour == "odd" then strLineColour = "even" else strLineColour = "odd" end
intWebLine = intWebLine + 1 -- V4.3
strWebLine = StrWebLineTemplate -- Replace web line placeholders with actual components below
strWebLine = strWebLine:replace("{LineColour}",strLineColour)
strWebLine = strWebLine:replace("{ItemNumber}",intWebLine) -- V4.3
for strLookupName, strWebAddress in pairs (dicLookupSite) do -- V4.3 -- Build search cell for each lookup web site
strWebLine = strWebLine:replace("{"..strLookupName.."}",StrBuildLookup(strWebAddress,tblProfile,strLookupName,ptrIndi,tblDetails))
end
strWebLine = strWebLine:replace("{Individual}",strIndiName) -- V4.1
strWebLine = strWebLine:replace("{Birth_Year}",strBirthYear)
strWebLine = strWebLine:replace("{Life_Dates}",tblDetails.Dates)
strWebLine = strWebLine:replace("{FamilyName}",encoder.StrGetItem_XML(tblDetails.FamLink,"~"))
strWebLine = strWebLine:replace("{FamilyDate}",tblDetails.FamDate)
strWebLine = strWebLine:replace("{FatherName}",strFathName)
strWebLine = strWebLine:replace("{MotherName}",strMothName)
putWebPage:write(strWebLine) -- Write grid line to HTML file -- V4.3
if strIndiNAME ~= tblIndiName[#tblIndiName] then -- Census Missing for new Individual Name so update Result Set -- V4.1 -- V4.9
strPosition = string.rep( " ", 4 - string.len(intWebLine) )..intWebLine -- V4.3
table.insert(tblPosition,strPosition)
table.insert(tblIndiName,strIndiNAME) -- V4.9
table.insert(tblIndiItem,ptrIndi:Clone())
table.insert(tblRecordId,intRecordId)
table.insert(tblLifeDate,( tblDetails.Dates:gsub(" "," ")) )
table.insert(tblGenApart,fhCallBuiltInFunction("RelationCode",ptrRecordRoot,ptrIndi,"GENERATION",1) or 99)
table.insert(tblRelation,fhCallBuiltInFunction("Relationship",ptrRecordRoot,ptrIndi,"TEXT",1))
table.insert(tblFathName,strFathNAME) -- V4.9
table.insert(tblFathItem,ptrFath:Clone())
table.insert(tblMothName,strMothNAME) -- V4.9
table.insert(tblMothItem,ptrMoth:Clone())
else
tblPosition[#tblPosition] = strPosition.." - "..intWebLine -- V4.3
end
end
end
end
end
end
if #tblIndiItem > 0 then -- Invoke browser web page and show Result Set
general.SaveStringToFile(StrWebPageJS,strFolder..strFile_js) -- Output the .js Java Script and .css Style Sheet
general.SaveStringToFile(StrWebPageCSS,strFolder..strFile_css)
putWebPage:write(StrWebTailTemplate)
putWebPage:close() -- Close and execute HTML output file -- V4.3
local isOK, intErrorCode, strErrorText = fhShellExecute(strFolder..strFile_html)
progbar.Step(1)
fhOutputResultSetTitles(strTitleName, strTitleName, "Printed Date: %#x")
fhOutputResultSetColumn("No." , "text" , tblPosition, #tblIndiItem, 50, "align_left" ) -- V4.1
fhOutputResultSetColumn("Individual", "text" , tblIndiName, #tblIndiItem, 140, "align_left")
fhOutputResultSetColumn("Individual", "item" , tblIndiItem, #tblIndiItem, 140, "align_left", 0, true, "default", "buddy" ) -- V4.1
fhOutputResultSetColumn("Rec Id" , "integer", tblRecordId, #tblIndiItem, 30, "align_mid" )
fhOutputResultSetColumn("Life Dates", "text" , tblLifeDate, #tblIndiItem, 50, "align_left") -- V4.1
fhOutputResultSetColumn("Gen. Gap" , "integer", tblGenApart, #tblIndiItem, 40, "align_mid" ) -- V4.1
local strColumn = "Relationship to "..fhGetDisplayText(ptrRecordRoot).." ["..fhGetRecordId(ptrRecordRoot).."]"
fhOutputResultSetColumn(strColumn , "text" , tblRelation, #tblIndiItem, 180, "align_left") -- V4.1
fhOutputResultSetColumn("Father" , "text" , tblFathName, #tblIndiItem, 140, "align_left")
fhOutputResultSetColumn("Father" , "item" , tblFathItem, #tblIndiItem, 140, "align_left", 0, true, "default", "buddy" ) -- V4.1
fhOutputResultSetColumn("Mother" , "text" , tblMothName, #tblIndiItem, 140, "align_left")
fhOutputResultSetColumn("Mother" , "item" , tblMothItem, #tblIndiItem, 140, "align_left", 0, true, "default", "buddy" ) -- V4.1
progbar.Close()
else
progbar.Close()
if #tblRecordList > 0 then
iup_gui.MemoDialogue("\n No "..strTitleName..". \n") -- No Census Missing informative message
end
end
end -- function RunLookup
-- Main code starts here --
fhInitialise(5,0,8,"save_recommended") -- Recommend current GEDCOM is saved if outstanding changes
PresetGlobalData() -- Preset global data definitions
ResetDefaultSettings() -- Preset default sticky settings
LoadSettings() -- Load sticky data settings
GUI_MainDialogue() -- Invoke User Dialogue
SaveSettings() -- Save sticky data settings
--[[
@TBD: Add USA State Census 1**5 years (https://en.wikipedia.org/wiki/State_censuses_in_the_United_States); Consider WWI USA Draft Registration Cards;
@V5.3: Library V3.9; Check for Updates button; Centre window on Parent FH window;
@V5.2: Added Canada.ca Library & Archives;
@V5.1: Added Ancestry UK 1921 and 1939 Census; Updated library to v3.7;
@V5.0: Fix CurrentFamily(...) spouse maiden name error; Updated library to v3.4;
@V4.9: Fix CacheFact(...) isInLocality bug; Fix name format in Result Set; Add Deutschland to Germany, Danmark to Denmark, Norge to Norway;
@V4.8: Check witnessed facts via INDI.~SHAR[i]>;
@V4.7: Updated library to Functions Prototypes v3.3; Updated for UK 1921 & USA 1950 Census; Updated various Census entries & URL templates; List all census records checked on a Help & Advice page;
@V4.6: Updated FamilySearch URL templates;
@V4.5: Updated library to Functions Prototypes v3.0; iup_gui.Balloon = "NO" for PlayOnLinux/Mac; Exclude "string quoted" place names; FH V7 Lua 3.5 IUP 3.28;
@V4.4: Updated library module for EstimatedDeathDates().
@V4.3: New TblRecordList() iteration loop, CensusMissing() new checks for home/abroad, cater for polygamy, new btnRedisplay, add Germany & Norway, cater for census dates not online, recognise Isle of Man & Channel Islands, updated library module for safer IUP GUI and EstimatedBirth/DeathDates().
@V4.2: Revised Ancestry URL USA Census codes, and USA & Canada Gender codes, plus FamilySearch and MyHeritage web sites added, use mid Age instead of max Age for tests, revised Template design for new StrBuildLookup().
@V4.1: Inhibit dialogue while running lookup, EstimatedBirthDates() for =EstimatedBirthDate() errors, DateExists() for Date Phrase no Date, exclude Short Names option, exclude partial Census Records option, extra Result Set columns, all Given names to FMP, latest Library.
@V4.0: CensusMissing() tolerant of Place case & spaces, cater for Null date, include Family Facts & sort Facts to curtail search unconditionally, include Record Id in browser page heading, fixed sorting with space => .
@V3.9: Add the 1939 Register of England&Wales for findmypast, and fix various other Relationship errors mainly in CurrentFamily() and StrRelatives().
@V3.8: Removed ".new" from "findmypast.co.uk".
@V3.7: Revised general & encoder library modules.
@V3.6: Both ANSI FH V5 & UTF-8 FH V6 IUP 3.11.2 encoder_v3, each encoder.StrANSI_XML/URI becomes encoder.StrEncode_XML/URI, and os.getenv("TEMP") becomes iup_gui.MachinePath.
@V3.5: Add BalloonToggle() right-click Set Window Fonts, search all Events if few Individuals, add Minimum Age threshold option for Child=false or EachCensus toggle,
@V3.5: added UK 1801/1821 Dartford Kent, USA 1890 Civil War Veterans Schedule, Ireland 1766, Canada 1825/1842 & 1906/1916 restricted to Manitoba, Saskatchewan, and Alberta.
@V3.4: Reinstate married women's surname of husband, make TblIndividualDetails() more efficient.
@V3.3: Fix droplist tooltips for XP, add UK Census 1790 & 1831 and Ireland Census 1790 to 1911, TblCensusProfile uses preset tables, better Census link labels, all columns sortable, all names use same format, fix home or abroad bugs.
@V3.2: Only add spouse if married and add parents if unmarried & younger than 21, conditionally add these family members & birth details & gender to findmypast searches,
@V3.2: use findmypast world search 'A-Z of Record Sets' mode, add 'year n/a' legend where Census unavailable instead of disabling buttons so useful list is always provided.
@V3.1: iup_gui module Help window X Close crash fix, extra fonts, new CheckVersionInStore, inhibit Minimize & Maximize in popups, new.findmypast.co.uk web search URL, etc, etc.
@V3.0: Optionally do not list as missing from Census if a Fact is found on the Census Date but not in the Census Locality, and cope with different country Census dates in same year.
@V2.9: Uses IsRelativeOf() as workaround for RelationPool() not working, plus related minor change to GUI, Version History help, Canadian 1921 Census, and new Library Modules.
@V2.8: Mother with a child, but father with no surname, retains original surname, and Family Date bug corrected.
@V2.7: EstimatedBirthDate() sets Birth Year & Range in search templates, Individual Name & Birth lookup more efficient, new FindMyPast Template codes,
new XML/HTML/URI/UTF encoding, DeleteOldFiles() modified, gsub() problems fixed with plain text versions, and revised web page layout.
@V2.6: Create js & css files from scripts in Plugin and delete 1-hour old temp js & css & html files, add FindMyPast USA, Ireland & Australia World Search for UK & USA Census.
@V2.5: Add 'Restore Defaults' and 'Set Window Font' and 'Help & Advice' and 'Relationship Pool' option, plus Australian Census, and all Ancestry web sites.
@ FHUG Work in Progress Versions:
@V2.4: Set GUI attributes according to options, remove gss= Mask for USA Ancestry, add option for Ancestors/Descendants/both/anyone.
@V2.3: Changed Canada Mask settings and many other minor updates to improve HTML and LUA style.
@V2.2: Plugin exits after Run Census Lookup, and HTML symbols are escaped.
@V2.1: Added Canada 1851, and incorporated Bill Henshaw's USA Census settings
@V2.0: Merge various similar Plugins into one with user options
@ Ancestor's UK Census Checker Versions:
@V1.3: Add Output to Result Set as well, to make it easier to find the Ancestors in the Local Database and support custom Census events in the xxxx Census naming format, additionally add sorting to the web output.
@V1.2: Change Find My Past Search to the new format
@V1.1: Added Find My Past Search
--]]
--[[
@Title: Lookup Missing Census Facts
@Type: Standard
@Author: Mike Tate
@Contributors:
@Version: 5.3
@Keywords:
@LastUpdated: 09 Feb 2026
@Licence: This plugin is copyright (c) 2026 Mike Tate & 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: Checks selected Individuals for chosen Census Events and produces a web page to search online databases for missing Census Records.
Estimated BMD dates are used, where none are recorded. Womens married names are used where the Census is after the marriage date (or after the birth of the first child).
@Version Log: See end of file.
]]
if fhGetAppVersion() > 5 then fhSetStringEncoding("UTF-8") end
--[[
@Title: aa Library Functions Preamble
@Author: Mike Tate
@Version: 3.9
@LastUpdated: 29 Jan 2026
@Description: All the library functions prototype closures for Plugins.
]]
--[[
@Module: +fh+stringx_v3
@Author: Mike Tate
@Version: 3.0
@LastUpdated: 19 Sep 2020
@Description: Extended string functions to supplement LUA string library.
@V3.0: Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility; Added inert(strTxt) function;
@V2.5: Support FH V6 Encoding = UTF-8;
@V2.4: Tolerant of integer & nil parameters just link match & gsub;
@V1.0: Initial version.
]]
local function stringx_v3()
local fh = {} -- Local environment table
-- Supply current file encoding format --
function fh.encoding()
if fhGetAppVersion() > 5 then return fhGetStringEncoding() end
return "ANSI"
end -- function encoding
-- Split a string using "," or chosen separator --
function fh.split(strTxt,strSep)
local tblFields = {}
local strPattern = string.format("([^%s]+)", strSep or ",")
strTxt = tostring(strTxt or "")
strTxt:gsub(strPattern, function(strField) tblFields[#tblFields+1] = strField end)
return tblFields
end -- function split
-- Split a string into numbers using " " or "," or "x" separators -- Any non-number remains as a string
function fh.splitnumbers(strTxt)
local tblNum = {}
strTxt = tostring(strTxt or "")
strTxt:gsub("([^ ,x]+)", function(strNum) tblNum[#tblNum+1] = tonumber(strNum) or strNum end)
return tblNum
end -- function splitnumbers
local strMagic = "([%^%$%(%)%%%.%[%]%*%+%-%?])" -- UTF-8 replacement for "(%W)"
-- Hide magic pattern symbols ^ $ ( ) % . [ ] * + - ?
function fh.plain(strTxt)
-- Prefix every magic pattern character with a % escape character,
-- where %% is the % escape, and %1 is the original character capture.
strTxt = tostring(strTxt or ""):gsub(strMagic,"%%%1")
return strTxt
end -- function plain
-- matches is plain text version of string.match()
function fh.matches(strTxt,strFind,intInit)
strFind = tostring(strFind or ""):gsub(strMagic,"%%%1") -- Hide magic pattern symbols
return tostring(strTxt or ""):match(strFind,tonumber(intInit))
end -- function matches
-- replace is plain text version of string.gsub()
function fh.replace(strTxt,strOld,strNew,intNum)
strOld = tostring(strOld or ""):gsub(strMagic,"%%%1") -- Hide magic pattern symbols
return tostring(strTxt or ""):gsub(strOld,function() return strNew end,tonumber(intNum)) -- Hide % capture symbols
end -- function replace
-- Hide % escape/capture symbols in replacement so they are inert
function fh.inert(strTxt)
strTxt = tostring(strTxt or ""):gsub("%%","%%%%") -- Hide all % symbols
return strTxt
end -- function inert
-- convert is pattern without captures version of string.gsub()
function fh.convert(strTxt,strOld,strNew,intNum)
return tostring(strTxt or ""):gsub(tostring(strOld or ""),function() return strNew end,tonumber(intNum)) -- Hide % capture symbols
end -- function convert
local dicUpper = { }
local dicLower = { }
local dicCaseX = { }
-- ASCII unaccented letter translations for Upper, Lower, and Case Insensitive
for intUpper = string.byte("A"), string.byte("Z") do
local strUpper = string.char(intUpper)
local strLower = string.char(intUpper - string.byte("A") + string.byte("a"))
dicUpper[strLower] = strUpper
dicLower[strUpper] = strLower
local strCaseX = "["..strUpper..strLower.."]"
dicCaseX[strLower] = strCaseX
dicCaseX[strUpper] = strCaseX
end
-- Supply character length of ANSI text --
function fh.length(strTxt)
return string.len(strTxt or "")
end -- function length
-- Supply character substring of ANSI text --
function fh.substring(strTxt,i,j)
return string.sub(strTxt or "",i,j)
end -- function substring
-- Translate upper/lower case ANSI letters to pattern that matches both --
function fh.caseless(strTxt)
strTxt = tostring(strTxt or ""):gsub("[A-Za-z]",dicCaseX)
return strTxt
end -- function caseless
if fh.encoding() == "UTF-8" then
-- Supply character length of UTF-8 text --
function fh.length(strTxt)
isFlag = fhIsConversionLossFlagSet()
strTxt = fhConvertUTF8toANSI(strTxt or "")
fhSetConversionLossFlag(isFlag)
return string.len(strTxt)
end -- function length
local strUTF8 = "([%z\1-\127\194-\244][\128-\191]*)" -- Cater for Lua 5.1 %z or Lua 5.3 \0
if fhGetAppVersion() > 6 then
strUTF8 = "([\0-\127\194-\244][\128-\191]*)"
end
-- Supply character substring of UTF-8 text --
function fh.substring(strTxt,i,j)
local strSub = ""
j = j or -1
if j < 0 then j = j + length(strTxt) + 1 end
if i < 0 then i = i + length(strTxt) + 1 end
for strChr in string.gmatch(strTxt or "",strUTF8) do
if j <= 0 then break end
j = j - 1
i = i - 1
if i <= 0 then strSub = strSub..strChr end
end
return strSub
end -- function substring
-- Translate lower case to upper case UTF-8 letters --
function fh.upper(strTxt)
strTxt = tostring(strTxt or ""):gsub("([a-z\194-\244][\128-\191]*)",dicUpper)
return strTxt
end -- function upper
-- Translate upper case to lower case UTF-8 letters --
function fh.lower(strTxt)
strTxt = tostring(strTxt or ""):gsub("([A-Z\194-\244][\128-\191]*)",dicLower)
return strTxt
end -- function lower
-- Translate upper/lower case UTF-8 letters to pattern that matches both --
function fh.caseless(strTxt)
strTxt = tostring(strTxt or ""):gsub("([A-Za-z\194-\244][\128-\191]*)",dicCaseX)
return strTxt
end -- function caseless
-- Following tables use ASCII numeric coding to be immune from ANSI/UTF-8 encoding --
local arrPairs = -- Upper & Lower case groups of UTF-8 letters with same prefix --
{-- { Prefix; Beg ; End ; Inc; Offset Upper > Lower }; -- These include all ANSI letters and many more
{ "\195"; 0x80; 0x96; 1 ; 32 }; -- 195=0xC3 À U+00C0 to Ö U+00D6 and à U+00E0 to ö U+00F6
{ "\195"; 0x98; 0x9E; 1 ; 32 }; -- 195=0xC3 Ø U+00D8 to Þ U+00DE and ø U+00F8 to þ U+00FE
{ "\196"; 0x80; 0xB6; 2 ; 1 }; -- 196=0xC4 A U+0100 to k U+0137 in pairs
{ "\196"; 0xB9; 0xBD; 2 ; 1 }; -- 196=0xC4 L U+0139 to l U+013E in pairs
{ "\197"; 0x81; 0x87; 2 ; 1 }; -- 197=0xC5 L U+0141 to n U+0148 in pairs
{ "\197"; 0x8A; 0xB6; 2 ; 1 }; -- 197=0xC5 ? U+014A to y U+0177 in pairs
{ "\197"; 0xB9; 0xBD; 2 ; 1 }; -- 197=0xC5 Z U+0179 to ž U+017E in pairs
{ "\198"; 0x82; 0x84; 2 ; 1 }; -- 198=0xC6 ? U+0182 to ? U+0185 in pairs
-- Add more Unicode groups here as usage increases --
}
local dicPairs = -- Upper v Lower case UTF-8 letters that don't fit groups above --
{ [string.char(0xC4,0xBF)] = string.char(0xC5,0x80); -- ? U+013F and ? U+0140
[string.char(0xC5,0xB8)] = string.char(0xC3,0xBF); -- Ÿ U+0178 and ÿ U+00FF
}
local intBeg1 = string.byte(string.sub("À",1))
local intBeg2 = string.byte(string.sub("À",2))
local intEnd1 = string.byte(string.sub("Z",1))
local intEnd2 = string.byte(string.sub("Z",2))
-- print(string.format("%#x %#x %#x %#x",intBeg1,intBeg2,intEnd1,intEnd2)) -- Useful to work out numeric coding
-- Populate the UTF-8 letter translation dictionaries --
for intGroup, tblGroup in ipairs ( arrPairs ) do -- UTF-8 accented letter groups
local strPrefix = tblGroup[1]
for intUpper = tblGroup[2], tblGroup[3], tblGroup[4] do
local strUpper = string.char(intUpper)
local strLower = string.char(intUpper + tblGroup[5])
local strCaseX = strPrefix.."["..strUpper..strLower.."]"
strUpper = strPrefix..strUpper
strLower = strPrefix..strLower
dicUpper[strLower] = strUpper
dicLower[strUpper] = strLower
dicCaseX[strLower] = strCaseX
dicCaseX[strUpper] = strCaseX
end
end
for strUpper, strLower in pairs ( dicPairs ) do -- UTF-8 accented letters where upper & lower have different prefix
dicUpper[strLower] = strUpper
dicLower[strUpper] = strLower
local strCaseX = ""
for intByte = 1, #strUpper do -- Matches more than just the two letters, but can't do any better
strCaseX = strCaseX.."["..strUpper:sub(intByte,intByte)..strLower:sub(intByte,intByte).."]"
end
dicCaseX[strLower] = strCaseX
dicCaseX[strUpper] = strCaseX
end
end
-- overload fh functions into string table
for strIndex, anyValue in pairs(fh) do
if type(anyValue) == "function" then
string[strIndex] = anyValue
end
end
return fh
end -- local function stringx_v3
local stringx = stringx_v3() -- To access FH string extension module
--[[
@Module: +fh+iterate_v3
@Author: Mike Tate
@Version: 3.0
@LastUpdated: 25 Aug 2020
@Description: An iterater functions module to supplement LUA functions.
@V3.0: Function Prototype Closure version.
@V1.2: RecordTypes() includes HEAD tag.
@V1.1: ?
@V1.0: Initial version.
]]
local function iterate_v3()
local fh = {} -- Local environment table
-- Iterator for all records of one chosen type --
function fh.Records(strType)
local ptrAll = fhNewItemPtr() -- Pointer to all records in turn
local ptrRec = fhNewItemPtr() -- Pointer to record returned to user
ptrAll:MoveToFirstRecord(strType)
return function ()
ptrRec:MoveTo(ptrAll)
ptrAll:MoveNext()
if ptrRec:IsNotNull() then return ptrRec end
end
end -- function Records
-- Iterator for all the record types --
function fh.RecordTypes()
local intNext = -1 -- Next record type number
local intLast = fhGetRecordTypeCount() -- Last record type number
return function()
intNext = intNext + 1
if intNext == 0 then -- Includes HEAD tag -- V1.2
return "HEAD"
elseif intNext <= intLast then
return fhGetRecordTypeTag(intNext) -- Return record type tag
end
end
end -- function RecordTypes
-- Iterator for all items in all records of chosen types --
function fh.Items(...)
local arg = {...}
local intType = 1 -- Integer record type number
local tblType = {} -- Table of record type tags
local ptrNext = fhNewItemPtr() -- Pointer to next item in turn
local ptrItem = fhNewItemPtr() -- Pointer to item returned to user
if #arg == 0 then
for intType = 1, fhGetRecordTypeCount() do -- No parameters so use all record types
tblType[intType] = fhGetRecordTypeTag(intType)
end
else
tblType = arg -- Got parameters so use them instead
end
-- print(tblType[intType],intType)
ptrNext:MoveToFirstRecord(tblType[intType]) -- Get first record of first type
return function()
repeat
while ptrNext:IsNotNull() do -- Loop through all items
ptrItem:MoveTo(ptrNext)
ptrNext:MoveNextSpecial()
if ptrItem:IsNotNull() then return ptrItem end
end
intType = intType + 1 -- Loop through each record type
if intType <= #tblType then
ptrNext:MoveToFirstRecord(tblType[intType])
end
until intType > #tblType
end
end -- function Items
-- Iterator for all facts of an individual --
function fh.Facts(ptrIndi)
local ptrItem = fhNewItemPtr() -- Pointer to each item at level 1
local ptrFact = fhNewItemPtr() -- Pointer to each fact returned to user
ptrItem:MoveToFirstChildItem(ptrIndi)
return function ()
while ptrItem:IsNotNull() do
ptrFact:MoveTo(ptrItem)
ptrItem:MoveNext()
if fhIsFact(ptrFact) then return ptrFact end
end
end
end -- function Facts
return fh
end -- local function iterate_v3
local iterate = iterate_v3() -- To access FH iterate items module
--[[
@Module: +fh+general_v3
@Author: Mike Tate
@Version: 3.5
@LastUpdated: 12 Dec 2024
@Description: A general functions module to supplement LUA functions, where filenames use UTF-8 but for a few exceptions.
@V3.5: Further fix for Crossover/WINE file attributes;
@V3.4: Further fix for Unix/WINE file attributes;
@V3.3: Fix problem in fh.MakeFolder(...) when folder path is invalid; Remove Type, ShortName & ShortPath from attributes(...) as unsupported in WINE;
@V3.2: Added function DetectOldModules(); Updated functions RenameFile(), RenameFolder() & GetFolderContents();
@V3.1: Functions derived from FH V7 fhFileUtils library using File System Objects, plus additional features;
@V3.0: Function Prototype Closure version; GetDayNumber() error message reasons;
@V1.5: Revised SplitFilename(strFilename) for missing extension.
@V1.4: Revised EstimatedBirthDates() & EstimatedDeathDates() to fix null Dates.
@V1.3: Add GetDayNumber(), EstimatedBirthDates(), EstimatedDeathDates().
@V1.2: SplitFilename() updated for directory only paths, and MakeFolder() added.
@V1.1: pl.path experiment revoked. New DirTree with omit branch option. Avoid using stringx_v2.
@V1.0: Initial version.
]]
local function general_v3()
local fh = {} -- Local environment table
require("luacom") -- To create File System Object
fh.FSO = luacom.CreateObject("Scripting.FileSystemObject")
-- Report error message --
local function doError(strMessage,errFunction)
-- strMessage ~ error message text
-- errFunction ~ optional error reporting function
if type(errFunction) == "function" then
errFunction(strMessage)
else
error(strMessage)
end
end -- local function doError
-- Convert filename to ANSI alternative and indicate success --
function fh.FileNameToANSI(strFileName,strAnsiName)
-- strFileName ~ full file path
-- strAnsiFile ~ ANSI file name & type
-- return values ~ ANSI file path, true if original path was ANSI compatible
if stringx.encoding() == "ANSI" then return strFileName, true end
local isFlag = fhIsConversionLossFlagSet()
fhSetConversionLossFlag(false)
local strAnsi = fhConvertUTF8toANSI(strFileName)
local wasAnsi = true
if fhIsConversionLossFlagSet() then
strAnsiName = strAnsiName or "ANSI.ANSI"
strAnsi = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugin Data\\"..strAnsiName
wasAnsi = false
end
fhSetConversionLossFlag(isFlag)
return strAnsi, wasAnsi
end -- local function FileNameToANSI
-- Get parent folder --
function fh.GetParentFolder(strFileName)
-- strFileName ~ full file path
-- return value ~ parent folder path
local strParent = fh.FSO:GetParentFolderName(strFileName) --! Faulty in FH v6 with Unicode chars in path
if fhGetAppVersion() == 6 then
local _, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
strParent = strFileName:match("^(.+)[\\/][^\\/]+[\\/]?$")
end
end
return strParent
end -- function GetParentFolder
-- Check if file exists --
function fh.FlgFileExists(strFileName)
-- strFileName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FileExists(strFileName)
end -- function FlgFileExists
-- Check if folder exists --
function fh.FlgFolderExists(strFolderName)
-- strFolderName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FolderExists(strFolderName)
end -- function FlgFolderExists
-- Delete a file if it exists --
function fh.DeleteFile(strFileName,errFunction)
-- strFileName ~ full file path
-- errFunction ~ optional error reporting function
-- return value ~ true if file does not exist or is deleted else false
if fh.FSO:FileExists(strFileName) then
fh.FSO:DeleteFile(strFileName,true)
if fh.FSO:FileExists(strFileName) then
doError("File Not Deleted:\n"..strFileName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFile
-- Delete a folder if it exists including contents --
function fh.DeleteFolder(strFolderName,errFunction)
-- strFolderName ~ full folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder does not exist or is deleted else false
if fh.FSO:FolderExists(strFolderName) then
fh.FSO:DeleteFolder(strFolderName,true)
if fh.FSO:FolderExists(strFolderName) then
doError("Folder Not Deleted:\n"..strFolderName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFolder
-- Rename a file if it exists --
function fh.RenameFile(strFileName,strNewName)
-- strFileName ~ full file path
-- strNewName ~ new file name & type
-- return value ~ true if file exists but new name does not and rename is OK else false
local strNewFile = fh.GetParentFolder(strFileName).."\\"..strNewName
if fh.FSO:FileExists(strFileName) and not fh.FSO:FileExists(strNewFile) then
local fileObject = fh.FSO:GetFile(strFileName)
fileObject.Name = strNewName
if fh.FSO:FileExists(strNewFile) then
return true
end
end
return false
end -- function RenameFile
-- Rename a folder if it exists --
function fh.RenameFolder(strFolderName,strNewName)
-- strFolderName ~ full folder path
-- strNewName ~ new folder name
-- return value ~ true if folder exists but new name does not and rename is OK else false
local strNewFolder = fh.GetParentFolder(strFolderName).."\\"..strNewName
if fh.FSO:FolderExists(strFolderName) and not fh.FSO:FolderExists(strNewFolder) then
local folderObject = fh.FSO:GetFolder(strFolderName)
folderObject.Name = strNewName
if fh.FSO:FolderExists(strNewFolder) then
return true
end
end
return false
end -- function RenameFolder
-- Copy a file if it exists and destination is not a folder --
function fh.CopyFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
fh.FSO:CopyFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
return false
end -- function CopyFile
-- Copy a folder if it exists and destination is not a file --
function fh.CopyFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
fh.FSO:CopyFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
return false
end -- function CopyFolder
-- Move a file if it exists and destination is not a folder --
function fh.MoveFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
if fh.DeleteFile(strDestination) then
fh.FSO:MoveFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFile
-- Move a folder if it exists and destination is not a file --
function fh.MoveFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
if fh.DeleteFolder(strDestination) then
fh.FSO:MoveFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFolder
local function CreateFolder(strFolderName) -- V3.3
fh.FSO:CreateFolder(strFolderName)
end -- local function CreateFolder(strFolderName)
-- Make subfolder recursively if does not exist --
function fh.MakeFolder(strFolderName,errFunction)
-- strFolderName ~ full source folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder exists or created else false
if not fh.FSO:FolderExists(strFolderName) then
if #strFolderName > 4 -- V3.3
and not fh.MakeFolder(fh.GetParentFolder(strFolderName),errFunction) then
return false
end
if not pcall(CreateFolder,strFolderName) -- V3.3
or not fh.FSO:FolderExists(strFolderName) then
doError("Cannot Make Folder Path: \n"..strFolderName.." \n",errFunction)
return false
end
end
return true
end -- function MakeFolder
-- Check if folder writable --
function fh.FlgFolderWrite(strFolderName)
-- strFolderName ~ full source folder path
-- return value ~ true if folder writable else false
if fh.FlgFolderExists(strFolderName) then
if fh.MakeFolder(strFolderName.."\\vwxyz") then
fh.FSO:DeleteFolder(strFolderName.."\\vwxyz",true)
return true
end
end
return false
end -- function FlgFolderWrite
-- Open File with ANSI path and return Handle --
function fh.OpenFile(strFileName,strMode)
-- strFileName ~ full file path
-- strMode ~ "r", "w", "a" optionally suffixed with "+" &/or "b"
-- return value ~ file handle
local fileHandle, strError = io.open(strFileName,strMode)
if fileHandle == nil then
error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..strError.." \n")
end
return fileHandle
end -- function OpenFile
-- Save string to file --
function fh.SaveStringToFile(strContents,strFileName,strFormat)
-- strContents ~ text string
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ true if successful else false
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhSaveTextFile(strFileName,strContents,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
local fileHandle = fh.OpenFile(strAnsi,"w")
fileHandle:write(strContents)
assert(fileHandle:close())
if not wasAnsi then
fh.MoveFile(strAnsi,strFileName)
end
return true
end -- function SaveStringToFile
-- Load string from file --
function fh.StrLoadFromFile(strFileName,strFormat)
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ file contents
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhLoadTextFile(strFileName,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
fh.CopyFile(strFileName,strAnsi)
end
local fileHandle = fh.OpenFile(strAnsi,"r")
local strContents = fileHandle:read("*all")
assert(fileHandle:close())
return strContents
end -- function StrLoadFromFile
-- Returns the Path, Filename, and Extension as 3 values --
function fh.SplitFilename(strFileName)
-- strFileName ~ full file path
-- return values ~ path, name.type, type
if fh.FSO:FolderExists(strFileName) then
local strPath = strFileName:gsub("[\\/]$","")
return strPath.."\\","",""
end
strFileName = strFileName.."."
return strFileName:match("^(.-)([^\\/]-%.([^\\/%.]-))%.?$")
end -- function SplitFilename
-- Convert dd/mm/yyyy hh:mm:ss format to integer seconds -- (DateTime format is used in attributes returned by GetFolderContents and DirTree below)
function fh.IntTime(strDateTime)
-- strDateTime ~ date time string
-- return value ~ integer seconds since 01/01/1970 00:00:00
local strDay,strMonth,strYear,strHour,strMin,strSec = strDateTime:match("^(%d%d)/(%d%d)/(%d+) (%d%d):(%d%d):(%d%d)")
if tonumber(strYear) < 1970 then return 0 end
local isDST = false
if tonumber(strMonth) > 4 and tonumber(strMonth) < 11 then isDST = true end -- Approximation is sometimes wrong
local intTime = os.time( { year=strYear; month=strMonth; day=strDay; hour=strHour; min=strMin; sec=strSec; isdst=isDST; } )
local tblDat = os.date("*t",intTime)
if tblDat.isdst then
intTime = intTime + 3600
isDST = true
end
return intTime
end -- function IntTime
-- Return table of attributes for folder --
local function attribFolder(tblAttr)
-- tblAttr ~ folder attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE only supports the tblAttr.name & tblAttr.path
return { mode="directory"; name=tblAttr.name; path=tblAttr.path; }
end -- local function attribFolder
-- Return table of attributes for file --
local function attribFile(tblAttr)
-- tblAttr ~ file attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE does not support the tblAttr.type, tblAttr.shortname, tblAttr.shortpath & sometimes tblAttr.datecreated
return { mode="file"; name=tblAttr.name; path=tblAttr.path; size=tblAttr.size; modified=tblAttr.datelastmodified; attributes=tblAttr.attributes; }
end -- local function attribFile
-- Return attributes table of all files and folders in a specified folder --
function fh.GetFolderContents(strFolder,doRecurse)
-- strFolder ~ full folder path
-- doRecurse ~ true for recursion
-- return value ~ attributes table
local arrList = {}
if fh.FSO:FolderExists(strFolder) then
local function getFileList(strFolder)
local tblList = fh.FSO:GetFolder(strFolder)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFolder(tblAttr))
if doRecurse then getFileList(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFile(tblAttr))
tblAttr = tblEnum:Next()
end
end
getFileList(strFolder)
end
return arrList
end -- function GetFolderContents
-- Return a Directory Tree entry & attributes on each iteration --
function fh.DirTree(strDir,...)
-- strDir ~ full folder path
-- ... ~ list of folders to omit
-- return value ~ full path, attributes table
local arg = {...}
assert( fh.FSO:FolderExists(strDir), "directory parameter is missing or empty" )
local function yieldtree(strDir)
local tblList = fh.FSO:GetFolder(strDir)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.SubFolders) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"directory"))
local isOK = true
for _,strOmit in ipairs (arg) do
if tblAttr.path:match(strOmit) then -- Omit tree branch
isOK = false
break
end
end
if isOK then yieldtree(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.Files) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"file"))
tblAttr = tblEnum:Next()
end
end
return coroutine.wrap(function() yieldtree(strDir) end)
end -- function DirTree
-- Detect FH V5/6 old library modules and advise removal --
function fh.DetectOldModules()
if fhGetAppVersion() > 6 then
local strPath = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugins\\"
local arrFile = { "compat53.lua"; "ltn12.lua"; "luasql\\sqlite3.dll"; "md5.lua"; "pl\\init.lua"; "socket.lua"; "utf8.lua"; "zip.dll"; }
for _, strFile in ipairs (arrFile) do
if fh.FSO:FileExists(strPath..strFile) then
fhMessageBox("\n Detected some old FH V6 library modules. \n\nPlease remove them by running the plugin: \n\n 'Delete old FH6 Plugin Module Files' \n","MB_OK","MB_ICONEXCLAMATION")
break
end
end
end
end -- function DetectOldModules
if fhGetAppVersion() > 6 then unpack = table.unpack end
-- Invoke FH Shell Execute API --
function fh.DoExecute(strExecutable,...)
-- strExecutable ~ full path of executable
-- ... ~ parameter list and optional error reporting function
-- return value ~ true if successful else false
local arg = {...}
local errFunction = fhMessageBox
if type(arg[#arg]) == 'function' then
errFunction = arg[#arg]
table.remove(arg)
end
local isOK, intErrorCode, strErrorText = fhShellExecute(strExecutable,unpack(arg))
if not isOK then
errFunction(tostring(strErrorText).." ("..tostring(intErrorCode)..")")
end
return isOK
end -- function DoExecute
-- Obtain the Day Number for any Date Point -- -- Fix problems with invalid dates in DayNumber function
function fh.GetDayNumber(datDate)
-- datDate ~ date point
-- return value ~ day number
if datDate:IsNull() then return 0 end
local intDay = fhCallBuiltInFunction("DayNumber",datDate) -- Only works for Gregorian dates that were not skipped nor BC dates
if not intDay then
local strError = "because " -- Error message reason -- V3.0
local calendar = datDate:GetCalendar()
local oldMonth = datDate:GetMonth()
local oldDayNo = datDate:GetDay()
local intMonth = math.min( oldMonth, 12 ) -- Limit month to 12, and day to last of each month
local intDayNo = math.min( oldDayNo, ({0;31;28;31;30;31;30;31;31;30;31;30;31;})[intMonth+1] )
local intYear = datDate:GetYear()
if oldDayNo > intDayNo then strError = strError.."day "..oldDayNo.." too big " end
if oldMonth > intMonth then strError = strError.."month "..oldMonth.." too big " end
if calendar == "Hebrew" and intYear > 3761 then
intYear = intYear - 3761
strError = strError.."Hebrew year > 3761 "
elseif calendar ~= "Gregorian" then
strError = strError..calendar.." disallowed "
end
if intYear == 1752 and intMonth == 9 and intDayNo <= 13 then -- Use 2 Sep 1752 for 3 - 13 Sep 1752 dates skipped
intDayNo = 2
strError = strError.."3 - 13 Sep 1752 skipped "
elseif intYear == 1582 and intMonth == 10 and intDayNo <= 14 then -- Use 4 Oct 1582 for 5 - 14 Oct 1582 dates skipped
intDayNo = 4
strError = strError.."5 - 14 Oct 1582 skipped "
end
local setDate = fhNewDatePt(intYear,intMonth,intDayNo,datDate:GetYearDD())
intDay = fhCallBuiltInFunction("DayNumber",setDate) -- Remove BC and Julian, Hebrew, French calendars
if not intDay then intDay = 0 end
local oldDate = fhNewDate() oldDate:SetSimpleDate(datDate) -- Report problem to user
local newDate = fhNewDate() newDate:SetSimpleDate(setDate)
local strIsBC = ""
if datDate:GetBC() then
strError = strError.." B.C. disallowed "
intDay = -intDay
strIsBC = "and Day Number negated"
end
fhMessageBox("\n Get Day Number issue for date \n "..oldDate:GetDisplayText().." \n "..strError.." \n So replaced it with date \n "..newDate:GetDisplayText().." \n "..strIsBC,"MB_OK","MB_ICONEXCLAMATION")
end
return intDay
end -- function GetDayNumber
local dtpYearMin = fhNewDatePt(1000) -- Minimum year to use when earliest estimate is null
local dtpYearMax = fhNewDatePt(2000) -- Maximum year to use when latest estimate is null
function fh.GetYearToday() -- Get the Year for Today
-- return value ~ integer year today
local intYearToday = fhCallBuiltInFunction("Year",fhCallBuiltInFunction("Today"))
dtpYearMax = fhNewDatePt(intYearToday) -- Set maximum year date point
return intYearToday
end -- function GetYearToday()
local function getDeathFacts(ptrIndi) -- Iterate Death, Burial, Cremation facts
-- ptrIndi ~ pointer to individual
-- return value ~ pointer to fact
local arrFact = { "~.DEAT"; "~.BURI"; "~.CREM"; }
local intFact = 0
local ptrFact = fhNewItemPtr() -- Pointer to each fact returned to user
return function ()
while intFact < #arrFact do
intFact = intFact + 1
ptrFact = fhGetItemPtr(ptrIndi,arrFact[intFact])
if ptrFact:IsNotNull() then return ptrFact end
end
end
end -- local function getDeathFacts
-- Ensure Estimated Date EARLIEST <= LATEST <= Fact Date -- -- Fix errors in EstimatedBirth/DeathDate function
local function estimatedDates(strFunc,ptrIndi,intGens,getFact,intYrs)
-- strFunc ~ "EstimatedBirthDate" or "EstimatedDeathDate"
-- ptrIndi ~ Individual of interest
-- intGens ~ Number of generations (may be nil)
-- getFact ~ Iterator function for facts
-- intYrs ~ Years to add to After dates
-- return values ~ EARLIEST, MID, LATEST dates
intGens = intGens or 2
local dtpMin = fhCallBuiltInFunction(strFunc,ptrIndi,"EARLIEST",intGens)
local dtpMax = fhCallBuiltInFunction(strFunc,ptrIndi,"LATEST",intGens)
local dtpMid = fhNewDatePt()
if not ( dtpMin:IsNull() and dtpMax:IsNull() ) then -- Skip if both null
if dtpMax:IsNull() then dtpMax = dtpYearMax elseif dtpMin:IsNull() then dtpMin = dtpYearMin end
for ptrFact in getFact(ptrIndi) do
local datFact = fhGetValueAsDate(fhGetItemPtr(ptrFact,"~.DATE"))
if not datFact:IsNull() then -- Find 1st Fact Date
local dtpLast = datFact:GetDatePt1() -- Last date = DatePt1 for Simple, Range, and Before
local strType = datFact:GetSubtype() -- Between = DatePt2 and After = DatePt1 + intYrs
if strType == "Between" then dtpLast = datFact:GetDatePt2()
elseif strType == "After" then dtpLast = fhNewDatePt(dtpLast:GetYear()+intYrs,dtpLast:GetMonth(),dtpLast:GetDay()) end -- Compare only uses Year, Month, Day so omitted ,dtpLast:GetYearDD(),dtpLast:GetBC(),dtpLast:GetCalendar()
if dtpMax:Compare(dtpLast) > 0 then dtpMax = dtpLast end
if dtpMin:Compare(dtpMax) > 0 then dtpMin = dtpMax end
if strType ~= "After" then break end -- Now EARLIEST <= LATEST <= Last date
end
end
local intDays = ( fh.GetDayNumber(dtpMax) - fh.GetDayNumber(dtpMin) ) / 2
local intYear,remYear = math.modf( intDays / 365.2422 ) -- Offset year @ 365.2422 days per year, and remainder fraction
local intMnth = math.floor( ( remYear * 12 ) + 0.1 ) -- Offset month is remainder fraction of year * 12
dtpMid = fhCallBuiltInFunction("CalcDate",dtpMin,intYear,intMnth) -- Need approximate MID year & month
end
return { Min=dtpMin; Mid=dtpMid; Max=dtpMax; } -- Return EARLIEST, MID, LATEST dates
end -- local function estimatedDates
-- Make EstimatedBirthDate EARLIEST <= LATEST <= 1st Fact Date -- -- Fix errors in EstimatedBirthDate function
function fh.EstimatedBirthDates(ptrIndi,intGens)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
return estimatedDates("EstimatedBirthDate",ptrIndi,intGens,iterate.Facts,10)
end -- function EstimatedBirthDates
-- Make EstimatedDeathDate EARLIEST <= LATEST <= DEAT/BURI/CREM Date -- -- Fix errors in EstimatedDeathDate function
function fh.EstimatedDeathDates(ptrIndi,intGens)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
return estimatedDates("EstimatedDeathDate",ptrIndi,intGens,getDeathFacts,100)
end -- function EstimatedDeathDates
--[[
@function: BuildDataRef
@description: Get Full Data Reference for Pointer
@parameters: Item Pointer
@returns: Data Reference String, Record Id Integer, Record Type Tag String
@requires: None
]]
function fh.BuildDataRef(ptrRef)
local strDataRef = "" -- Data Reference with instance indices e.g. INDI.RESI[2].ADDR
local intRecId = 0 -- Record Id for associated Record
local strRecTag = "" -- Record Tag of associated Record type i.e. INDI, FAM, NOTE, SOUR, etc
-- getDataRef() is called recursively per level of the Data Ref
-- ptrRef points to the upper Data Ref levels yet to be analysed
-- strRef compiles the lower Data Ref levels including instances
local function getDataRef(ptrRef,strRef)
local ptrTag = ptrRef:Clone()
local strTag = fhGetTag(ptrTag) -- Current level Tag
ptrTag:MoveToParentItem(ptrTag)
if ptrTag:IsNotNull() then -- Parent level exists
local intSib = 1
local ptrSib = ptrRef:Clone() -- Pointer to siblings with same Tag
ptrSib:MovePrev("SAME_TAG")
while ptrSib:IsNotNull() do -- Count previous siblings with same Tag
intSib = intSib + 1
ptrSib:MovePrev("SAME_TAG")
end
if intSib > 1 then strTag = strTag.."["..intSib.."]" end
getDataRef(ptrTag,"."..strTag..strRef) -- Now analyse the parent level
else
strDataRef = strTag..strRef -- Record level reached, so set return values
intRecId = fhGetRecordId(ptrRef)
strRecTag = strTag
if not fhIsValidDataRef(strDataRef) then print("BuildDataRef: "..strDataRef.." is Invalid") end
end
end -- local function getDataRef
if type(ptrRef) == "userdata" then getDataRef(ptrRef,"") end
return strDataRef, intRecId, strRecTag
end -- function BuildDataRef
--[[
@function: GetDataRefPtr
@description: Get Pointer for Full Data Reference
@parameters: Data Reference String, Record Id Integer, Record Type Tag String (optional)
@returns: Item Pointer which IsNull() if any parameters are invalid
@requires: None
]]
function fh.GetDataRefPtr(strDataRef,intRecId,strRecTag)
strDataRef = strDataRef or ""
if not strRecTag then
strRecTag = strDataRef:gsub("^(%u+).*$","%1") -- Extract Record Tag from Data Ref
end
local ptrRef = fhNewItemPtr()
ptrRef:MoveToRecordById(strRecTag,intRecId or 0) -- Lookup the Record by Id
ptrRef:MoveTo(ptrRef,strDataRef) -- Move to the Data Ref
return ptrRef
end -- function GetDataRefPtr
function fh.TblDataRef(ptrRef)
local tblRef = {}
tblRef.DataRef, tblRef.RecId, tblRef.RecTag = BuildDataRef(ptrRef)
return tblRef
end -- function TblDataRef
function fh.PtrDataRef(tblRef)
local tblRef = tblRef or {} -- Ensure table and its fields exist
return GetDataRefPtr(tblRef.DataRef or "",tblRef.RecId or 0,tblRef.RecTag or "")
end -- function PtrDataRef
return fh
end -- local function general_v3
local general = general_v3() -- To access FH general tools module
--[[
@Module: +fh+tablex_v3
@Author: Mike Tate
@Version: 3.1
@LastUpdated: 08 Jan 2022
@Description: A Table Load Save Module.
@V3.1: Cater for full UTF-8 filenames.
@V3.0: Function Prototype Closure version.
@V1.2: Added local definitions of _ to ensure nil gets returned on error.
@V1.1: ?
@V1.0: Initial version 0.94 is Lua 5.1 compatible.
]]
local function tablex_v3()
local fh = {} -- Local environment table
------------------------------------------------------ Start Table Load Save
-- require "_tableloadsave"
--[[
Save Table to File/Stringtable
Load Table from File/Stringtable
v 0.94
Lua 5.1 compatible
Userdata and indices of these are not saved
Functions are saved via string.dump, so make sure it has no upvalues
References are saved
----------------------------------------------------
table.save( table [, filename] )
Saves a table so it can be called via the table.load function again
table must a object of type 'table'
filename is optional, and may be a string representing a filename or true/1
table.save( table )
on success: returns a string representing the table (stringtable)
(uses a string as buffer, ideal for smaller tables)
table.save( table, true or 1 )
on success: returns a string representing the table (stringtable)
(uses io.tmpfile() as buffer, ideal for bigger tables)
table.save( table, "filename" )
on success: returns 1
(saves the table to file "filename")
on failure: returns as second argument an error msg
----------------------------------------------------
table.load( filename or stringtable )
Loads a table that has been saved via the table.save function
on success: returns a previously saved table
on failure: returns as second argument an error msg
----------------------------------------------------
chillcode, http://lua-users.org/wiki/SaveTableToFile
Licensed under the same terms as Lua itself.
]]--
-- declare local variables
--// exportstring( string )
--// returns a "Lua" portable version of the string
local function exportstring( s )
s = string.format( "%q",s )
-- to replace
s = string.gsub( s,"\\\n","\\n" )
s = string.gsub( s,"\r","\\r" )
s = string.gsub( s,string.char(26),"\"..string.char(26)..\"" )
return s
end
--// The Save Function
function fh.save( tbl,filename )
local charS,charE = " ","\n"
local file,err,_,stransi,wasansi -- V1.2 -- V3.1 -- Added _,stransi,wasansi --!
-- create a pseudo file that writes to a string and return the string
if not filename then
file = { write = function( self,newstr ) self.str = self.str..newstr end, str = "" }
charS,charE = "",""
-- write table to tmpfile
elseif filename == true or filename == 1 then
charS,charE,file = "","",io.tmpfile()
-- write table to file
-- use io.open here rather than io.output, since in windows when clicking on a file opened with io.output will create an error
else
stransi,wasansi = general.FileNameToANSI(filename) -- V3.1 -- Cater for non-ANSI filename --!
file,err = io.open( stransi, "w" )
if err then return _,err end
end
-- initiate variables for save procedure
local tables,lookup = { tbl },{ [tbl] = 1 }
file:write( "return {"..charE )
for idx,t in ipairs( tables ) do
if filename and filename ~= true and filename ~= 1 then
file:write( "-- Table: {"..idx.."}"..charE )
end
file:write( "{"..charE )
local thandled = {}
for i,v in ipairs( t ) do
thandled[i] = true
-- escape functions and userdata
if type( v ) ~= "userdata" then
-- only handle value
if type( v ) == "table" then
if not lookup[v] then
table.insert( tables, v )
lookup[v] = #tables
end
file:write( charS.."{"..lookup[v].."},"..charE )
elseif type( v ) == "function" then
file:write( charS.."loadstring("..exportstring(string.dump( v )).."),"..charE )
else
local value = ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
file:write( charS..value..","..charE )
end
end
end
for i,v in pairs( t ) do
-- escape functions and userdata
if (not thandled[i]) and type( v ) ~= "userdata" then
-- handle index
if type( i ) == "table" then
if not lookup[i] then
table.insert( tables,i )
lookup[i] = #tables
end
file:write( charS.."[{"..lookup[i].."}]=" )
else
local index = ( type( i ) == "string" and "["..exportstring( i ).."]" ) or string.format( "[%d]",i )
file:write( charS..index.."=" )
end
-- handle value
if type( v ) == "table" then
if not lookup[v] then
table.insert( tables,v )
lookup[v] = #tables
end
file:write( "{"..lookup[v].."},"..charE )
elseif type( v ) == "function" then
file:write( "loadstring("..exportstring(string.dump( v )).."),"..charE )
else
local value = ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
file:write( value..","..charE )
end
end
end
file:write( "},"..charE )
end
file:write( "}" )
-- Return Values
-- return stringtable from string
if not filename then
-- set marker for stringtable
return file.str.."--|"
-- return stringttable from file
elseif filename == true or filename == 1 then
file:seek ( "set" )
-- no need to close file, it gets closed and removed automatically
-- set marker for stringtable
return file:read( "*a" ).."--|"
-- close file and return 1
else
file:close()
if not ( wasansi ) then -- V3.1 -- Cater for non-ANSI filename --!
general.MoveFile(stransi,filename)
end
return 1
end
end
--// The Load Function
function fh.load( sfile )
local tables,err,_ -- V1.2 -- Added _
-- catch marker for stringtable
if string.sub( sfile,-3,-1 ) == "--|" then
tables,err = loadstring( sfile )
else
local stransi,wasansi = general.FileNameToANSI(sfile) -- V3.1 -- Cater for non-ANSI filename --!
if not ( wasansi ) then
general.CopyFile(sfile,stransi)
end
tables,err = loadfile( stransi )
if not ( wasansi ) then
general.DeleteFile(stransi) -- V3.1 -- Cater for non-ANSI filename --!
end
end
if err then return _,err
end
tables = tables()
for idx = 1,#tables do
local tolinkv,tolinki = {},{}
for i,v in pairs( tables[idx] ) do
if type( v ) == "table" and tables[v[1]] then
table.insert( tolinkv,{ i,tables[v[1]] } )
end
if type( i ) == "table" and tables[i[1]] then
table.insert( tolinki,{ i,tables[i[1]] } )
end
end
-- link values, first due to possible changes of indices
for _,v in ipairs( tolinkv ) do
tables[idx][v[1]] = v[2]
end
-- link indices
for _,v in ipairs( tolinki ) do
tables[idx][v[2]],tables[idx][v[1]] = tables[idx][v[1]],nil
end
end
return tables[1]
end
------------------------------------------------------ End Table Load Save
-- overload fh functions into table
for strIndex, anyValue in pairs(fh) do
if type(anyValue) == "function" then
table[strIndex] = anyValue
end
end
return fh
end -- local function tablex_v3
local tablex = tablex_v3 () -- To access FH table extension module
--[[
@Module: +fh+encoder_v3
@Author: Mike Tate
@Version: 3.6
@LastUpdated: 27 Aug 2024
@Description: Text encoder module for HTML XHTML XML URI UTF8 UTF16 ISO CP1252/ANSI character codings.
@V3.6: In fh.FileLines(...) cater for empty file;
@V3.5: Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility.
@V3.4: Ensure expressions involving gsub return just text parameter.
@V3.3: Adds UNICODE U+10000 to U+10FFFF UTF-16 Supplementary Planes.
@V3.2: Update for ANSI & Unicode to ASCII for sorting, Soundex, etc.
@V3.1: Update for Unicode UTF-16 & UTF-8 and fhConvertANSItoUTF8 & fhConvertUTF8toANSI, name change UTF to UTF8 & CP to ANSI.
@V2.0: StrUTF8_Encode() replaced by StrUTF_CP1252() for entire UTF-8 range, plus new StrCP1252_ISO().
@V1.0: Initial version.
]]
local function encoder_v3()
local fh = {} -- Local environment table
local fhVersion = fhGetAppVersion()
local br_Tag = "
" -- Markup language break tag default
local br_Lua = "
" -- Lua pattern for break tag recognition
local tblCodePage = {} -- Code Page to XML/XHTML/HTML/URI/UTF8 encodings: http://en.wikipedia.org/wiki/Windows-1252 & 1250 & etc
-- Control characters "\000" to "\031" for URI & Markup "[%c]" encodings are disallowed except for "\t" to "\r"
tblCodePage["\000"] = "" -- NUL
tblCodePage["\001"] = "" -- SOH
tblCodePage["\002"] = "" -- STX
tblCodePage["\003"] = "" -- ETX
tblCodePage["\004"] = "" -- EOT
tblCodePage["\005"] = "" -- ENQ
tblCodePage["\006"] = "" -- ACK
tblCodePage["\a"] = "" -- BEL
tblCodePage["\b"] = "" -- BS
tblCodePage["\t"] = "+" -- HT space in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["\n"] = "%0A" -- LF br_Tag in Markup
tblCodePage["\v"] = "%0A" -- VT br_Tag in Markup
tblCodePage["\f"] = "%0A" -- FF br_Tag in Markup
tblCodePage["\r"] = "%0D" -- CR br_Tag in Markup
tblCodePage["\014"] = "" -- SO
tblCodePage["\015"] = "" -- SI
tblCodePage["\016"] = "" -- DLE
tblCodePage["\017"] = "" -- DC1
tblCodePage["\018"] = "" -- DC2
tblCodePage["\019"] = "" -- DC3
tblCodePage["\020"] = "" -- DC4
tblCodePage["\021"] = "" -- NAK
tblCodePage["\022"] = "" -- SYN
tblCodePage["\023"] = "" -- ETB
tblCodePage["\024"] = "" -- CAN
tblCodePage["\025"] = "" -- EM
tblCodePage["\026"] = "" -- SUB
tblCodePage["\027"] = "" -- ESC
tblCodePage["\028"] = "" -- FS
tblCodePage["\029"] = "" -- GS
tblCodePage["\030"] = "" -- RS
tblCodePage["\031"] = "" -- US
-- ASCII characters "\032" to "\127" for URI "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding
tblCodePage[" "] = "+" -- or "%20" Space
tblCodePage["!"] = "%21" -- Reserved character
tblCodePage['"'] = "%22" -- """ in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["#"] = "%23" -- Reserved character
tblCodePage["$"] = "%24" -- Reserved character
tblCodePage["%"] = "%25" -- Must be encoded
tblCodePage["&"] = "%26" -- Reserved character -- "&" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["'"] = "%27" -- Reserved character -- "'" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["("] = "%28" -- Reserved character
tblCodePage[")"] = "%29" -- Reserved character
tblCodePage["*"] = "%2A" -- Reserved character
tblCodePage["+"] = "%2B" -- Reserved character
tblCodePage[","] = "%2C" -- Reserved character
-- tblCodePage["-"] = "%2D" -- Unreserved character not encoded
-- tblCodePage["."] = "%2E" -- Unreserved character not encoded
tblCodePage["/"] = "%2F" -- Reserved character
-- Digits 0 to 9 -- Unreserved characters not encoded
tblCodePage[":"] = "%3A" -- Reserved character
tblCodePage[";"] = "%3B" -- Reserved character
tblCodePage["<"] = "%3C" -- "<" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["="] = "%3D" -- Reserved character
tblCodePage[">"] = "%3E" -- ">" in Markup see setURIEncodings() and setMarkupEncodings() below
tblCodePage["?"] = "%3F" -- Reserved character
tblCodePage["@"] = "%40" -- Reserved character
-- Letters A to Z -- Unreserved characters not encoded
tblCodePage["["] = "%5B" -- Reserved character
tblCodePage["\\"]= "%5C"
tblCodePage["]"] = "%5D" -- Reserved character
tblCodePage["^"] = "%5E"
-- tblCodePage["_"] = "%5F" -- Unreserved character not encoded
tblCodePage["`"] = "%60"
-- Letters a to z -- Unreserved characters not encoded
tblCodePage["{"] = "%7B"
tblCodePage["|"] = "%7C"
tblCodePage["}"] = "%7D"
-- tblCodePage["~"] = "%7E" -- Unreserved character not encoded
tblCodePage["\127"] = "" -- DEL
-- Code Page 1252 Unicode characters "\128" to "\255" for UTF-8 scheme "[€-ÿ]" encodings: http://en.wikipedia.org/wiki/UTF-8
tblCodePage["€"] = string.char(0xE2,0x82,0xAC) -- "€"
tblCodePage["\129"] = "" -- Undefined
tblCodePage["‚"] = string.char(0xE2,0x80,0x9A)
tblCodePage["ƒ"] = string.char(0xC6,0x92)
tblCodePage["„"] = string.char(0xE2,0x80,0x9E)
tblCodePage["…"] = string.char(0xE2,0x80,0xA6)
tblCodePage["†"] = string.char(0xE2,0x80,0xA0)
tblCodePage["‡"] = string.char(0xE2,0x80,0xA1)
tblCodePage["ˆ"] = string.char(0xCB,0x86)
tblCodePage["‰"] = string.char(0xE2,0x80,0xB0)
tblCodePage["Š"] = string.char(0xC5,0xA0)
tblCodePage["‹"] = string.char(0xE2,0x80,0xB9)
tblCodePage["Œ"] = string.char(0xC5,0x92)
tblCodePage["\141"] = "" -- Undefined
tblCodePage["Ž"] = string.char(0xC5,0xBD)
tblCodePage["\143"] = "" -- Undefined
tblCodePage["\144"] = "" -- Undefined
tblCodePage["‘"] = string.char(0xE2,0x80,0x98)
tblCodePage["’"] = string.char(0xE2,0x80,0x99)
tblCodePage["“"] = string.char(0xE2,0x80,0x9C)
tblCodePage["”"] = string.char(0xE2,0x80,0x9D)
tblCodePage["•"] = string.char(0xE2,0x80,0xA2)
tblCodePage["–"] = string.char(0xE2,0x80,0x93)
tblCodePage["—"] = string.char(0xE2,0x80,0x94)
tblCodePage["\152"] = string.char(0xCB,0x9C) -- Small Tilde
tblCodePage["™"] = string.char(0xE2,0x84,0xA2)
tblCodePage["š"] = string.char(0xC5,0xA1)
tblCodePage["›"] = string.char(0xE2,0x80,0xBA)
tblCodePage["œ"] = string.char(0xC5,0x93)
tblCodePage["\157"] = "" -- Undefined
tblCodePage["ž"] = string.char(0xC5,0xBE)
tblCodePage["Ÿ"] = string.char(0xC5,0xB8)
tblCodePage["\160"] = string.char(0xC2,0xA0) -- " " No Break Space
tblCodePage["¡"] = string.char(0xC2,0xA1) -- "¡"
tblCodePage["¢"] = string.char(0xC2,0xA2) -- "¢"
tblCodePage["£"] = string.char(0xC2,0xA3) -- "£"
tblCodePage["¤"] = string.char(0xC2,0xA4) -- "¤"
tblCodePage["¥"] = string.char(0xC2,0xA5) -- "¥"
tblCodePage["¦"] = string.char(0xC2,0xA6)
tblCodePage["§"] = string.char(0xC2,0xA7)
tblCodePage["¨"] = string.char(0xC2,0xA8)
tblCodePage["©"] = string.char(0xC2,0xA9)
tblCodePage["ª"] = string.char(0xC2,0xAA)
tblCodePage["«"] = string.char(0xC2,0xAB)
tblCodePage["¬"] = string.char(0xC2,0xAC)
tblCodePage[""] = string.char(0xC2,0xAD) -- "" Soft Hyphen
tblCodePage["®"] = string.char(0xC2,0xAE)
tblCodePage["¯"] = string.char(0xC2,0xAF)
tblCodePage["°"] = string.char(0xC2,0xB0)
tblCodePage["±"] = string.char(0xC2,0xB1)
tblCodePage["²"] = string.char(0xC2,0xB2)
tblCodePage["³"] = string.char(0xC2,0xB3)
tblCodePage["´"] = string.char(0xC2,0xB4)
tblCodePage["µ"] = string.char(0xC2,0xB5)
tblCodePage["¶"] = string.char(0xC2,0xB6)
tblCodePage["·"] = string.char(0xC2,0xB7)
tblCodePage["¸"] = string.char(0xC2,0xB8)
tblCodePage["¹"] = string.char(0xC2,0xB9)
tblCodePage["º"] = string.char(0xC2,0xBA)
tblCodePage["»"] = string.char(0xC2,0xBB)
tblCodePage["¼"] = string.char(0xC2,0xBC)
tblCodePage["½"] = string.char(0xC2,0xBD)
tblCodePage["¾"] = string.char(0xC2,0xBE)
tblCodePage["¿"] = string.char(0xC2,0xBF)
tblCodePage["À"] = string.char(0xC3,0x80)
tblCodePage["Á"] = string.char(0xC3,0x81)
tblCodePage["Â"] = string.char(0xC3,0x82)
tblCodePage["Ã"] = string.char(0xC3,0x83)
tblCodePage["Ä"] = string.char(0xC3,0x84)
tblCodePage["Å"] = string.char(0xC3,0x85)
tblCodePage["Æ"] = string.char(0xC3,0x86)
tblCodePage["Ç"] = string.char(0xC3,0x87)
tblCodePage["È"] = string.char(0xC3,0x88)
tblCodePage["É"] = string.char(0xC3,0x89)
tblCodePage["Ê"] = string.char(0xC3,0x8A)
tblCodePage["Ë"] = string.char(0xC3,0x8B)
tblCodePage["Ì"] = string.char(0xC3,0x8C)
tblCodePage["Í"] = string.char(0xC3,0x8D)
tblCodePage["Î"] = string.char(0xC3,0x8E)
tblCodePage["Ï"] = string.char(0xC3,0x8F)
tblCodePage["Ð"] = string.char(0xC3,0x90)
tblCodePage["Ñ"] = string.char(0xC3,0x91)
tblCodePage["Ò"] = string.char(0xC3,0x92)
tblCodePage["Ó"] = string.char(0xC3,0x93)
tblCodePage["Ô"] = string.char(0xC3,0x94)
tblCodePage["Õ"] = string.char(0xC3,0x95)
tblCodePage["Ö"] = string.char(0xC3,0x96)
tblCodePage["×"] = string.char(0xC3,0x97)
tblCodePage["Ø"] = string.char(0xC3,0x98)
tblCodePage["Ù"] = string.char(0xC3,0x99)
tblCodePage["Ú"] = string.char(0xC3,0x9A)
tblCodePage["Û"] = string.char(0xC3,0x9B)
tblCodePage["Ü"] = string.char(0xC3,0x9C)
tblCodePage["Ý"] = string.char(0xC3,0x9D)
tblCodePage["Þ"] = string.char(0xC3,0x9E)
tblCodePage["ß"] = string.char(0xC3,0x9F)
tblCodePage["à"] = string.char(0xC3,0xA0)
tblCodePage["á"] = string.char(0xC3,0xA1)
tblCodePage["â"] = string.char(0xC3,0xA2)
tblCodePage["ã"] = string.char(0xC3,0xA3)
tblCodePage["ä"] = string.char(0xC3,0xA4)
tblCodePage["å"] = string.char(0xC3,0xA5)
tblCodePage["æ"] = string.char(0xC3,0xA6)
tblCodePage["ç"] = string.char(0xC3,0xA7)
tblCodePage["è"] = string.char(0xC3,0xA8)
tblCodePage["é"] = string.char(0xC3,0xA9)
tblCodePage["ê"] = string.char(0xC3,0xAA)
tblCodePage["ë"] = string.char(0xC3,0xAB)
tblCodePage["ì"] = string.char(0xC3,0xAC)
tblCodePage["í"] = string.char(0xC3,0xAD)
tblCodePage["î"] = string.char(0xC3,0xAE)
tblCodePage["ï"] = string.char(0xC3,0xAF)
tblCodePage["ð"] = string.char(0xC3,0xB0)
tblCodePage["ñ"] = string.char(0xC3,0xB1)
tblCodePage["ò"] = string.char(0xC3,0xB2)
tblCodePage["ó"] = string.char(0xC3,0xB3)
tblCodePage["ô"] = string.char(0xC3,0xB4)
tblCodePage["õ"] = string.char(0xC3,0xB5)
tblCodePage["ö"] = string.char(0xC3,0xB6)
tblCodePage["÷"] = string.char(0xC3,0xB7)
tblCodePage["ø"] = string.char(0xC3,0xB8)
tblCodePage["ù"] = string.char(0xC3,0xB9)
tblCodePage["ú"] = string.char(0xC3,0xBA)
tblCodePage["û"] = string.char(0xC3,0xBB)
tblCodePage["ü"] = string.char(0xC3,0xBC)
tblCodePage["ý"] = string.char(0xC3,0xBD)
tblCodePage["þ"] = string.char(0xC3,0xBE)
tblCodePage["ÿ"] = string.char(0xC3,0xBF)
-- Set XML/XHTML/HTML "[%c\"&'<>]" Markup encodings: http://en.wikipedia.org/wiki/XML and http://en.wikipedia.org/wiki/HTML
local function setMarkupEncodings()
tblCodePage["\t"] = " " -- HT "\t" to "\r" are treated as white space in Markup Languages by default
tblCodePage["\n"] = br_Tag -- LF
tblCodePage["\v"] = br_Tag -- VT line break tag "
" or "
" or "
" or "
" is better
tblCodePage["\f"] = br_Tag -- FF
tblCodePage["\r"] = br_Tag -- CR
tblCodePage['"'] = """
tblCodePage["&"] = "&"
tblCodePage["'"] = "'"
tblCodePage["<"] = "<"
tblCodePage[">"] = ">"
end -- local function setMarkupEncodings
-- Set URI/URL/URN "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding
local function setURIEncodings()
tblCodePage["\t"] = "+" -- HT space
tblCodePage["\n"] = "%0A" -- LF newline
tblCodePage["\v"] = "%0A" -- VT newline
tblCodePage["\f"] = "%0A" -- FF newline
tblCodePage["\r"] = "%0D" -- CR return
tblCodePage['"'] = "%22"
tblCodePage["&"] = "%26"
tblCodePage["'"] = "%27"
tblCodePage["<"] = "%3C"
tblCodePage[">"] = "%3E"
end -- local function setURIEncodings
-- Encode characters according to gsub pattern & lookup table --
local function strEncode(strText,strPattern,tblPattern)
return ( (strText or ""):gsub(strPattern,tblPattern) ) -- V3.4
end -- local function strEncode
-- Encode CP1252/ANSI characters into UTF-8 codes --
function fh.StrANSI_UTF8(strText)
if fhVersion > 5 then
strText = fhConvertANSItoUTF8(strText)
else
strText = strEncode(strText,"[\127-ÿ]",tblCodePage)
end
return strText
end -- function StrANSI_UTF8
function fh.StrCP_UTF(strText) -- Legacy
return fh.StrANSI_UTF8(strText)
end -- function StrCP1252_UTF8
function fh.StrCP1252_UTF(strText) -- Legacy
return fh.StrANSI_UTF8(strText)
end -- function StrCP1252_UTF
-- Encode CP1252/ANSI or UTF-8 characters into UTF-8 --
function fh.StrEncode_UTF8(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_UTF8(strText)
else
return strText
end
end -- function StrEncode_UTF8
-- Encode CP1252/ANSI characters into XML/XHTML/HTML/UTF8 codes --
local strANSI_XML = "[%z\001-\031\"&'<>\127-ÿ]"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strANSI_XML = "[\000-\031\"&'<>\127-ÿ]"
end
function fh.StrANSI_XML(strText)
setMarkupEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag
strText = strEncode(strText,strANSI_XML,tblCodePage)
return strText
end -- function StrANSI_XML
function StrCP_XML(strText) -- Legacy
return fh.StrANSI_XML(strText)
end -- function StrCP_XML
function StrCP1252_XML(strText) -- Legacy
return fh.StrANSI_XML(strText)
end -- function StrCP1252_XML
-- Encode UTF-8 ASCII characters into XML/XHTML/HTML codes --
local strUTF8_XML = "[%z\001-\031\"&'<>\127]"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strUTF8_XML = "[\000-\031\"&'<>\127]"
end
function fh.StrUTF8_XML(strText)
setMarkupEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag
strText = strEncode(strText,strUTF8_XML,tblCodePage)
return strText
end -- function StrUTF8_XML
-- Encode CP1252/ANSI or UTF-8 ASCII characters into XML/XHTML/HTML codes --
function fh.StrEncode_XML(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_XML(strText)
else
return fh.StrUTF8_XML(strText)
end
end -- function StrEncode_XML
-- Encode Item Text characters into XML/HTML/UTF-8 codes --
function fh.StrGetItem_XML(ptrItem,strTags)
return fh.StrEncode_XML(fhGetItemText(ptrItem,strTags))
end -- function StrGetItem_XML
-- Encode CP1252/ANSI characters into URI codes --
function fh.StrANSI_URI(strText)
setURIEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes %0A
strText = strEncode(strText,"[^0-9A-Za-z]",tblCodePage)
return strText
end -- function StrANSI_URI
function fh.StrCP_URI(strText)
return fh.StrANSI_URI(strText)
end -- function StrCP_URI
function fh.StrCP1252_URI(strText)
return fh.StrANSI_URI(strText)
end -- function StrCP1252_URI
-- Encode UTF-8 ASCII characters into URI codes --
local strUTF8_URI = "[%z\001-\127]"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strUTF8_URI = "[\000-\127]"
end
function fh.StrUTF8_URI(strText)
setURIEncodings()
strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag
strText = strEncode(strText,strUTF8_URI,tblCodePage)
return strText
end -- function StrUTF8_URI
-- Encode CP1252/ANSI or UTF-8 ASCII characters into URI codes --
function fh.StrEncode_URI(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_URI(strText)
else
return fh.StrUTF8_URI(strText)
end
end -- function StrEncode_URI
function fh.StrUTF8_Encode(strText) -- Legacy from V1.0
return fh.StrUTF8_ANSI(strText)
end -- function StrUTF8_Encode
-- Encode UTF-8 bytes into single CP1252/ANSI character V2.0 upvalues --
local strByteRange = "["..string.char(0xC0).."-"..string.char(0xFF).."]"
local tblBytePoint = {0xC0;0xE0;0xF0;0xF8;0xFC;} -- Byte codes for 2-byte, 3-byte, 4-byte, 5-byte, 6-byte UTF-8
local tblUTF8 = {}
for strByte = string.byte("€"), string.byte("ÿ") do
local strChar = string.char(strByte) -- Use CodePage to UTF-8 table to populate UTF-8 to CodePage table
local strCode = tblCodePage[strChar]
tblUTF8[strCode] = strChar
end
-- Encode UTF-8 bytes into single CP1252/ANSI character --
function fh.StrUTF8_ANSI(strText)
strText = strText or ""
if fhVersion > 5 then return fhConvertUTF8toANSI(strText) end
if strText:match(strByteRange) then -- If text contains characters that need translating then
local intChar = 0 -- Input character index
local strChar = "" -- Current character
local strCode = "" -- UTF-8 multi-byte code
local tblLine = {} -- Translated output line
repeat
intChar = intChar + 1 -- Step through each character in text
strChar = strText:sub(intChar,intChar)
if strChar:match(strByteRange) then -- Convert UTF-8 bytes into CP character
strCode = strChar -- First UTF-8 byte code, whose top bits say how many bytes to append
for intByte, strByte in ipairs(tblBytePoint) do
if string.byte(strChar) >= strByte then
intChar = intChar + 1 -- Append next UTF-8 byte code character
strCode = strCode..strText:sub(intChar,intChar)
else
break
end
end
strChar = tblUTF8[strCode] or "¿" -- Translate UTF-8 code into CP character
end
table.insert(tblLine,strChar) -- Accumulate output char by char
until intChar >= #strText
strText = table.concat(tblLine)
end
return strText
end -- function StrUTF8_ANSI
function fh.StrUTF_CP(strText) -- Legacy
return fh.StrUTF8_ANSI(strText)
end -- function StrUTF_CP
function fh.StrUTF_CP1252(strText) -- Legacy
return fh.StrUTF8_ANSI(strText)
end -- function StrUTF_CP1252
-- Encode CP1252/ANSI or UTF-8 characters into ANSI --
function fh.StrEncode_ANSI(strText)
if stringx.encoding() == "ANSI" then
return strText or ""
else
return fh.StrUTF8_ANSI(strText)
end
end -- function StrEncode_ANSI
-- Set ISO-8859-1 "[\127-Ÿ]" encodings: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
local tblISO8859 = { }
tblISO8859["\127"]="" -- DEL
tblISO8859["€"] = "EUR"
tblISO8859["\129"]="" -- Undefined
tblISO8859["‚"] = "¸"
tblISO8859["ƒ"] = "f"
tblISO8859["„"] = "¸¸"
tblISO8859["…"] = "..."
tblISO8859["†"] = "+"
tblISO8859["‡"] = "±"
tblISO8859["ˆ"] = "^"
tblISO8859["‰"] = "%"
tblISO8859["Š"] = "S"
tblISO8859["‹"] = "<"
tblISO8859["Œ"] = "OE"
tblISO8859["\141"]="" -- Undefined
tblISO8859["Ž"] = "Z"
tblISO8859["\143"]="" -- Undefined
tblISO8859["\144"]="" -- Undefined
tblISO8859["‘"] = "'"
tblISO8859["’"] = "'"
tblISO8859["“"] = '"'
tblISO8859["”"] = '"'
tblISO8859["•"] = "º"
tblISO8859["–"] = "-"
tblISO8859["—"] = "-"
tblISO8859["\152"]="~" -- Small Tilde
tblISO8859["™"] = "TM"
tblISO8859["š"] = "s"
tblISO8859["›"] = ">"
tblISO8859["œ"] = "oe"
tblISO8859["\157"]="" -- Undefined
tblISO8859["ž"] = "z"
tblISO8859["Ÿ"] = "Y"
-- Encode CP1252/ANSI characters into ISO-8859-1 codes --
function fh.StrANSI_ISO(strText)
return strEncode(strText,"[\127-Ÿ]",tblISO8859)
end -- function StrANSI_ISO
function fh.StrCP_ISO(strText) -- Legacy
return fh.StrANSI_ISO(strText)
end -- function StrCP_ISO
function fh.StrCP1252_ISO(strText) -- Legacy
return fh.StrANSI_ISO(strText)
end -- function StrCP1252_ISO
function fh.StrUTF8_ISO(strText)
return fh.StrANSI_ISO(fh.StrUTF8_ANSI(strText))
end -- function StrUTF8_ISO
-- Encode CP1252/ANSI or UTF-8 ASCII characters into ISO-8859-1 codes --
function fh.StrEncode_ISO(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_ISO(strText)
else
return fh.StrUTF8_ISO(strText)
end
end -- function StrEncode_ISO
-- Convert UTF-8 bytes to a UTF-16 word or pair --
local tblByte = {}
local tblLead = { 0x80; 0xC0; 0xE0; 0xF0; 0xF8; 0xFC; }
function fh.StrUtf8toUtf16(strChar)
-- Convert any UTF-8 multibytes to UTF-16 --
local function strUtf8()
if #tblByte > 0 then
local intUtf16 = 0
for intIndex, intByte in ipairs (tblByte) do -- Convert UTF-8 bytes to UNICODE U+0080 to U+10FFFF
if intIndex == 1 then
intUtf16 = intByte - tblLead[#tblByte]
else
intUtf16 = intUtf16 * 0x40 + intByte - 0x80
end
end
if intUtf16 > 0xFFFF then -- U+10000 to U+10FFFF Supplementary Planes -- V2.6
tblByte = {}
intUtf16 = intUtf16 - 0x10000
local intLow10 = 0xDC00 + ( intUtf16 % 0x400 ) -- Low 16-bit Surrogate
local intTop10 = 0xD800 + math.floor( intUtf16 / 0x400 ) -- High 16-bit Surrogate
local intChar1 = intTop10 % 0x100
local intChar2 = math.floor( intTop10 / 0x100 )
local intChar3 = intLow10 % 0x100
local intChar4 = math.floor( intLow10 / 0x100 )
return string.char(intChar1,intChar2,intChar3,intChar4) -- Surrogate 16-bit Pair
end
if intUtf16 < 0xD800 -- U+0080 to U+FFFF (except U+D800 to U+DFFF) -- V2.6
or intUtf16 > 0xDFFF then -- Basic Multilingual Plane
tblByte = {}
local intChar1 = intUtf16 % 0x100
local intChar2 = math.floor( intUtf16 / 0x100 )
return string.char(intChar1,intChar2) -- BPL 16-bit
end
local strUtf8 = "" -- U+D800 to U+DFFF Reserved Code Points -- V2.6
for intIndex, intByte in ipairs (tblByte) do
strUtf8 = strUtf8..string.format("%.2X ",intByte)
end
local strUtf16 = string.format("%.4X ",intUtf16)
fhMessageBox("\n UTF-16 Reserved Code Point U+D800 to U+DFFF \n UTF-16 = "..strUtf16.." UTF-8 = "..strUtf8.."\n Character will be replaced by a question mark. \n","MB_OK","MB_ICONEXCLAMATION")
tblByte = {}
return "?\0"
end
return ""
end -- local function strUtf8
local intUtf8 = string.byte(strChar)
if intUtf8 < 0x80 then -- U+0000 to U+007F (ASCII)
return strUtf8()..strChar.."\0" -- Previous UTF-8 multibytes + current ASCII char
end
if intUtf8 >= 0xC0 then -- Next UTF-8 multibyte start
local strUtf16 = strUtf8()
table.insert(tblByte,intUtf8)
return strUtf16 -- Previous UTF-8 multibytes
end
table.insert(tblByte,intUtf8)
return ""
end -- function StrUtf8toUtf16
-- Encode UTF-8 bytes into UTF-16 words --
function fh.StrUTF8_UTF16(strText)
tblByte = {} -- (0xFF) flushes last UTF-8 character
return ( ((strText or "")..string.char(0xFF)):gsub("(.)",fh.StrUtf8toUtf16) ) -- V3.4
end -- function StrUTF8_UTF16
-- Encode CP1252/ANSI or UTF-8 characters into UTF-16 words --
function fh.StrEncode_UTF16(strText)
if stringx.encoding() == "ANSI" then
strText = fh.StrANSI_UTF8(strText)
end
return fh.StrUTF8_UTF16(strText)
end -- function StrEncode_UTF16
local intTop10 = 0
-- Convert a UTF-16 word or pair to UTF-8 bytes --
function fh.StrUtf16toUtf8(strChar1,strChar2)
local intUtf16 = string.byte(strChar2) * 0x100 + string.byte(strChar1)
if intUtf16 < 0x80 then -- U+0000 to U+007F (ASCII)
return string.char(intUtf16)
end
if intUtf16 < 0x800 then -- U+0080 to U+07FF
local intByte1 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte2 = intUtf16
return string.char( intByte2 + 0xC0, intByte1 + 0x80 )
end
if intUtf16 < 0xD800 -- U+0800 to U+FFFF
or intUtf16 > 0xDFFF then
local intByte1 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte2 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte3 = intUtf16
return string.char( intByte3 + 0xE0, intByte2 + 0x80, intByte1 + 0x80 )
end
if intUtf16 < 0xDC00 then -- U+10000 to U+10FFFF High 16-bit Surrogate Supplementary Planes -- V2.6
intTop10 = ( intUtf16 - 0xD800 ) * 0x400 + 0x10000
return ""
end
intUtf16 = intUtf16 - 0xDC00 + intTop10 -- U+10000 to U+10FFFF Low 16-bit Surrogate Supplementary Planes -- V2.6
local intByte1 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte2 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte3 = intUtf16 % 0x40
intUtf16 = math.floor( intUtf16 / 0x40 )
local intByte4 = intUtf16
return string.char( intByte4 + 0xF0, intByte3 + 0x80, intByte2 + 0x80, intByte1 + 0x80 )
end -- function StrUtf16toUtf8
-- Encode UTF-16 words into UTF-8 bytes --
function fh.StrUTF16_UTF8(strText)
return ( (strText or ""):gsub("(.)(.)",fh.StrUtf16toUtf8) ) -- V3.4
end -- function StrUTF16_UTF8
-- Encode UTF-16 words into ANSI characters --
function fh.StrUTF16_ANSI(strText)
return fh.StrUTF8_ANSI(fh.StrUTF16_UTF8(strText))
end -- function StrUTF16_ANSI
-- Read UTF-16/UTF-8/ANSI file converted to chosen encoding via line iterator --
local strUtf16 = "^.%z"
if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0
strUtf16 = "^.\0"
end
function fh.FileLines(strFileName,strEncoding) -- Derived from http://lua-users.org/wiki/EnhancedFileLines
local bomUtf16= "^"..string.char(0xFF,0xFE) -- "ÿþ"
local bomUtf8 = "^"..string.char(0xEF,0xBB,0xBF) -- ""
local fncConv = tostring -- Function to convert input to current encoding
local intHead = 1 -- Index to start of current text line
local intLump = 1024
local fHandle = general.OpenFile(strFileName,"rb")
local strText = fHandle:read(1024) or "" -- Read first lump from file and cater for empty file
local intBOM = 0
strEncoding = strEncoding or string.encoding()
if strText:match(bomUtf16)
or strText:match(strUtf16) then
strText,intBOM = strText:gsub(bomUtf16,"") -- Strip UTF-16 BOM
if strEncoding == "ANSI" then -- Define UTF-16 conversion to current encoding
fncConv = fh.StrUTF16_ANSI
else
fncConv = fh.StrUTF16_UTF8
end
elseif strText:match(bomUtf8) then
strText,intBOM = strText:gsub(bomUtf8,"") -- Strip UTF-8 BOM
if strEncoding == "ANSI" then -- Define UTF-8 conversion to current encoding
fncConv = fh.StrUTF8_ANSI
end
else
if strEncoding == "UTF-8" and #strText > 0 then -- Define ANSI conversion to current encoding and cater for empty file
fncConv = fh.StrANSI_UTF8
end
end
strText = fncConv(strText) -- Convert first lump of text
return function() -- Iterator function
local intTail,strTail -- Index to end of current text line, and terminating characters
while true do
intTail, strTail = strText:match("()([\r\n].)",intHead)
if intTail or not fHandle then
if intHead > 1 then intLump = 0 end
break -- End of line or end of file
elseif fHandle then
local strLump = fHandle:read(1024) -- Read next lump from file
if strLump then -- Strip old text and add converted lump
strText = strText:sub(intHead)..fncConv(strLump)
intHead = 1
intLump = 1024
else
assert(fHandle:close()) -- End of file
fHandle = nil
end
end
end
if not intTail then
intTail = #strText -- Last fragment of file
elseif strTail == "\r\n" then
intTail = intTail + 1 -- Adjust tail for both \r & \n
end
local strLine = strText:sub(intHead,intTail) -- Extract line from text
intHead = intTail + 1
if #strLine > 0 then -- Return pruned line, tail chars, lump bytes read
local strBody, strTail = strLine:match("^(.-)([\r\n]+)$")
return strBody, strTail, intLump
end
end
end -- function FileLines
-- Set "[€-ÿ]" ASCII encodings same as Unidecode below
local tblASCII = { }
tblASCII["€"] = "=E"
tblASCII["\129"]="" -- Undefined
tblASCII["‚"] = ","
tblASCII["ƒ"] = "f"
tblASCII["„"] = ",,"
tblASCII["…"] = "..."
tblASCII["†"] = "|+"
tblASCII["‡"] = "|++"
tblASCII["ˆ"] = "^"
tblASCII["‰"] = "%0"
tblASCII["Š"] = "S"
tblASCII["‹"] = "<"
tblASCII["Œ"] = "OE"
tblASCII["\141"]="" -- Undefined
tblASCII["Ž"] = "Z"
tblASCII["\143"]="" -- Undefined
tblASCII["\144"]="" -- Undefined
tblASCII["‘"] = "'"
tblASCII["’"] = "'"
tblASCII["“"] = "\""
tblASCII["”"] = "\""
tblASCII["•"] = "*"
tblASCII["–"] = "-"
tblASCII["—"] = "--"
tblASCII["\152"]="~" -- Small Tilde
tblASCII["™"] = "TM"
tblASCII["š"] = "s"
tblASCII["›"] = ">"
tblASCII["œ"] = "oe"
tblASCII["\157"]="" -- Undefined
tblASCII["ž"] = "z"
tblASCII["Ÿ"] = "Y"
tblASCII["\160"]=" " -- " " No Break Space
tblASCII["¡"] = "!" -- "¡"
tblASCII["¢"] = "=c" -- "¢"
tblASCII["£"] = "=L" -- "£"
tblASCII["¤"] = "=$" -- "¤"
tblASCII["¥"] = "=Y" -- "¥"
tblASCII["¦"] = "|"
tblASCII["§"] = "=SS"
tblASCII["¨"] = "\""
tblASCII["©"] = "(C)"
tblASCII["ª"] = "a"
tblASCII["«"] = "<<"
tblASCII["¬"] = "-"
tblASCII[""] = "-" -- "" Soft Hyphen
tblASCII["®"] = "(R)"
tblASCII["¯"] = "-"
tblASCII["°"] = "=o"
tblASCII["±"] = "+-"
tblASCII["²"] = "2"
tblASCII["³"] = "3"
tblASCII["´"] = "'"
tblASCII["µ"] = "=u"
tblASCII["¶"] = "=p"
tblASCII["·"] = "*"
tblASCII["¸"] = ","
tblASCII["¹"] = "1"
tblASCII["º"] = "o"
tblASCII["»"] = ">>"
tblASCII["¼"] = "1/4"
tblASCII["½"] = "1/2"
tblASCII["¾"] = "3/4"
tblASCII["¿"] = "?"
tblASCII["À"] = "A"
tblASCII["Á"] = "A"
tblASCII["Â"] = "A"
tblASCII["Ã"] = "A"
tblASCII["Ä"] = "A"
tblASCII["Å"] = "A"
tblASCII["Æ"] = "AE"
tblASCII["Ç"] = "C"
tblASCII["È"] = "E"
tblASCII["É"] = "E"
tblASCII["Ê"] = "E"
tblASCII["Ë"] = "E"
tblASCII["Ì"] = "I"
tblASCII["Í"] = "I"
tblASCII["Î"] = "I"
tblASCII["Ï"] = "I"
tblASCII["Ð"] = "D"
tblASCII["Ñ"] = "N"
tblASCII["Ò"] = "O"
tblASCII["Ó"] = "O"
tblASCII["Ô"] = "O"
tblASCII["Õ"] = "O"
tblASCII["Ö"] = "O"
tblASCII["×"] = "*"
tblASCII["Ø"] = "O"
tblASCII["Ù"] = "U"
tblASCII["Ú"] = "U"
tblASCII["Û"] = "U"
tblASCII["Ü"] = "U"
tblASCII["Ý"] = "Y"
tblASCII["Þ"] = "TH"
tblASCII["ß"] = "ss"
tblASCII["à"] = "a"
tblASCII["á"] = "a"
tblASCII["â"] = "a"
tblASCII["ã"] = "a"
tblASCII["ä"] = "a"
tblASCII["å"] = "a"
tblASCII["æ"] = "ae"
tblASCII["ç"] = "c"
tblASCII["è"] = "e"
tblASCII["é"] = "e"
tblASCII["ê"] = "e"
tblASCII["ë"] = "e"
tblASCII["ì"] = "i"
tblASCII["í"] = "i"
tblASCII["î"] = "i"
tblASCII["ï"] = "i"
tblASCII["ð"] = "d"
tblASCII["ñ"] = "n"
tblASCII["ò"] = "o"
tblASCII["ó"] = "o"
tblASCII["ô"] = "o"
tblASCII["õ"] = "o"
tblASCII["ö"] = "o"
tblASCII["÷"] = "/"
tblASCII["ø"] = "o"
tblASCII["ù"] = "u"
tblASCII["ú"] = "u"
tblASCII["û"] = "u"
tblASCII["ü"] = "u"
tblASCII["ý"] = "y"
tblASCII["þ"] = "th"
tblASCII["ÿ"] = "y"
-- Encode CP1252/ANSI characters into ASCII codes [\000-\127] --
function fh.StrANSI_ASCII(strText)
return strEncode(strText,"[€-ÿ]",tblASCII)
end -- function StrANSI_ASCII
--[=[
Unidecode converts each codepoint into a few ASCII characters.
Lookup table indexed by codepoint [0x0000]-[0xFFFF] gives an ASCII string.
i.e. strASCII = Unidecode[intByte2][intByte1] or "=?" allowing for partially populated table.
See http://search.cpan.org/dist/Text-Unidecode/ and follow Browse to:
See http://cpansearch.perl.org/src/SBURKE/Text-Unidecode-1.22/lib/Text/Unidecode/
where each x??.pm gives 256 ASCII conversions.
Start with the first few European accented characters, and add the others later.
--]=]
local Unidecode = { }
function fh.StrUnidecode(strChar1,strChar2) -- Decode UTF-16 byte pair into ASCII characters
return Unidecode[string.byte(strChar2)][string.byte(strChar1)] or "=?"
end -- function StrUnidecode
-- Encode UTF-8 characters into ASCII codes [\000-\126] --
function fh.StrUTF8_ASCII(strText)
strText = fh.StrUTF8_UTF16(strText) -- Convert to UTF-16 Unicode and then to ASCII
return ( strText:gsub("(.)(.)",fh.StrUnidecode) )
end -- function StrUTF8_ASCII
-- Encode CP1252/ANSI or UTF-8 into ASCII codes [\000-\126] --
function fh.StrEncode_ASCII(strText)
if stringx.encoding() == "ANSI" then
return fh.StrANSI_ASCII(strText)
else
return fh.StrUTF8_ASCII(strText)
end
end -- function StrEncode_ASCII
-- Set markup language break tag --
function fh.SetBreakTag(br_New)
if not (br_New or ""):match(br_Lua) then -- Ensure new break tag is "
" or "
" or "
" or "
"
br_New = "
"
end
br_Tag = br_New
end -- function SetBreakTag
for intByte = 0x00, 0xFF do Unidecode[intByte] = { } end
Unidecode[0x00] =
{[0]="\00";"\01";"\02";"\03";"\04";"\05";"\06";"\a";"\b";"\t";"\n";"\v";"\f";"\r";"\14";"\15";"\16";"\17";"\18";"\19";"\20";"\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\30";"\31";
" ";"!";'"';"#";"$";"%";"&";"'";"(";")";"*";"+";",";"-";".";"/";"0";"1";"2";"3";"4";"5";"6";"7";"8";"9";":";";";"<";"=";">";"?"; -- 0x20 to 0x3F
"@";"A";"B";"C";"D";"E";"F";"G";"H";"I";"J";"K";"L";"M";"N";"O";"P";"Q";"R";"S";"T";"U";"V";"W";"X";"Y";"Z";"[";"\\";"]";"^";"_"; -- 0x40 to 0x5F
"`";"a";"b";"c";"d";"e";"f";"g";"h";"i";"j";"k";"l";"m";"n";"o";"p";"q";"r";"s";"t";"u";"v";"w";"x";"y";"z";"{";"|";"}";"~";"\127"; -- 0x60 to 0x7F
""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; -- 0x80 to 0x9F
" ";"!";"=c";"=L";"=$";"=Y";"|";"=SS";'"';"(C)";"a";"<<";"-";"-";"(R)";"-";"=o";"+-";"2";"3";"'";"=u";"=P";"*";",";"1";"o";">>";"1/4";"1/2";"3/4";"?"; -- 0xA0 to 0xBF
"A";"A";"A";"A";"A";"A";"AE";"C";"E";"E";"E";"E";"I";"I";"I";"I";"D";"N";"O";"O";"O";"O";"O";"*";"O";"U";"U";"U";"U";"Y";"TH";"ss"; -- 0xC0 to 0xDF
"a";"a";"a";"a";"a";"a";"ae";"c";"e";"e";"e";"e";"i";"i";"i";"i";"d";"n";"o";"o";"o";"o";"o";"/";"o";"u";"u";"u";"u";"y";"th";"y"; -- 0xE0 to 0xFF
}
Unidecode[0x01] =
{[0]="A";"a";"A";"a";"A";"a";"C";"c";"C";"c";"C";"c";"C";"c";"D";"d";"D";"d";"E";"e";"E";"e";"E";"e";"E";"e";"E";"e";"G";"g";"G";"g"; -- 0x00 to 0x1F
"G";"g";"G";"g";"H";"h";"H";"h";"I";"i";"I";"i";"I";"i";"I";"i";"I";"i";"IJ";"ij";"J";"j";"K";"k";"k";"L";"l";"L";"l";"L";"l";"L"; -- 0x20 to 0x3F
"l";"L";"l";"N";"n";"N";"n";"N";"n";"'n";"ng";"NG";"O";"o";"O";"o";"O";"o";"OE";"oe";"R";"r";"R";"r";"R";"r";"S";"s";"S";"s";"S";"s"; -- 0x40 to 0x5F
"S";"s";"T";"t";"T";"t";"T";"t";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"W";"w";"Y";"y";"Y";"Z";"z";"Z";"z";"Z";"z";"s"; -- 0x60 to 0x7F
"b";"B";"B";"b";"6";"6";"O";"C";"c";"D";"D";"D";"d";"d";"3";"@";"E";"F";"f";"G";"G";"hv";"I";"I";"K";"k";"l";"l";"W";"N";"n";"O"; -- 0x80 to 0x9F
"O";"o";"OI";"oi";"P";"p";"YR";"2";"2";"SH";"sh";"t";"T";"t";"T";"U";"u";"Y";"V";"Y";"y";"Z";"z";"ZH";"ZH";"zh";"zh";"2";"5";"5";"ts";"w"; -- 0xA0 to 0xBF
"|";"||";"|=";"!";"DZ";"Dz";"dz";"LJ";"Lj";"lj";"NJ";"Nj";"nj";"A";"a";"I";"i";"O";"o";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"@";"A";"a"; -- 0xC0 to 0xDF
"A";"a";"AE";"ae";"G";"g";"G";"g";"K";"k";"O";"o";"O";"o";"ZH";"zh";"j";"DZ";"Dz";"dz";"G";"g";"HV";"W";"N";"n";"A";"a";"AE";"ae";"O";"o"; -- 0xE0 to 0xFF
}
Unidecode[0x02] =
{[0]="A";"a";"A";"a";"E";"e";"E";"e";"I";"i";"I";"i";"O";"o";"O";"o";"R";"r";"R";"r";"U";"u";"U";"u";"S";"s";"T";"t";"Y";"y";"H";"h"; -- 0x00 to 0x1F
"N";"d";"OU";"ou";"Z";"z";"A";"a";"E";"e";"O";"o";"O";"o";"O";"o";"O";"o";"Y";"y";"l";"n";"t";"j";"db";"qp";"A";"C";"c";"L";"T";"s"; -- 0x20 to 0x3F
"z";"[?]";"[?]";"B";"U";"^";"E";"e";"J";"j";"q";"q";"R";"r";"Y";"y";"a";"a";"a";"b";"o";"c";"d";"d";"e";"@";"@";"e";"e";"e";"e";"j"; -- 0x40 to 0x5F
"g";"g";"g";"g";"u";"Y";"h";"h";"i";"i";"I";"l";"l";"l";"lZ";"W";"W";"m";"n";"n";"n";"o";"OE";"O";"F";"r";"r";"r";"r";"r";"r";"r"; -- 0x60 to 0x7F
"R";"R";"s";"S";"j";"S";"S";"t";"t";"u";"U";"v";"^";"w";"y";"Y";"z";"z";"Z";"Z";"?";"?";"?";"C";"@";"B";"E";"G";"H";"j";"k";"L"; -- 0x80 to 0x9F
"q";"?";"?";"dz";"dZ";"dz";"ts";"tS";"tC";"fN";"ls";"lz";"WW";"]]";"h";"h";"h";"h";"j";"r";"r";"r";"r";"w";"y";"'";'"';"`";"'";"`";"`";"'"; -- 0xA0 to 0xBF
"?";"?";"<";">";"^";"V";"^";"V";"'";"-";"/";"\\";",";"_";"\\";"/";":";".";"`";"'";"^";"V";"+";"-";"V";".";"@";",";"~";'"';"R";"X"; -- 0xC0 to 0xDF
"G";"l";"s";"x";"?";"";"";"";"";"";"";"";"V";"=";'"';"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF
}
Unidecode[0x03] =
{
}
Unidecode[0x04] =
{
}
Unidecode[0x20] =
{[0]=" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";"";"";"";"";"-";"-";"-";"-";"--";"--";"||";"_";"'";"'";",";"'";'"';'"';",,";'"'; -- 0x00 to 0x1F
"|+";"|++";"*";"*>";".";"..";"...";".";"\n";"\n\n";"";"";"";"";"";" ";"%0";"%00";"'";"''";"'''";"`";"``";"```";"^";"<";">";"*";"!!";"!?";"-";"_"; -- 0x20 to 0x3F
"-";"^";"***";"--";"/";"-[";"]-";"[?]";"?!";"!?";"7";"PP";"(]";"[)";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x40 to 0x5F
"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"0";"";"";"";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"n"; -- 0x60 to 0x7F
"0";"1";"2";"3";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x80 to 0x9F
"ECU";"CL";"Cr";"FF";"L";"mil";"N";"Pts";"Rs";"W";"NS";"D";"=E";"K";"T";"Dr";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xA0 to 0xBF
"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";""; -- 0xC0 to 0xDF
"";"";"";"";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF
}
Unidecode[0x21] =
{[34]="TM";
}
return fh
end -- local function encoder_v3
local encoder = encoder_v3() -- To access FH encoder chars module
--[[
@Module: +fh+progbar_v3
@Author: Mike Tate
@Version: 3.1
@LastUpdated: 23 Jan 2026
@Description: Progress Bar library module.
@V3.1: Use NATIVEPARENT amd CENTERPARENT.
@V3.0: Function Prototype Closure version.
@V1.0: Initial version.
]]
local function progbar_v3()
local fh = {} -- Local environment table
require "iuplua" -- To access GUI window builder
iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28
local tblBars = {} -- Table for optional external attributes
local strBack = "255 255 255" -- Background colour default is white
local strBody = "0 0 0" -- Body text colour default is black
local strFont = nil -- Font dialogue default is current font
local strStop = "255 0 0" -- Stop button colour default is red
local intPosX = iup.CENTERPARENT -- Show window default position is central -- V3.1
local intPosY = iup.CENTERPARENT
local intMax, intVal, intPercent, intStart, intDelta, intScale, strClock, isBarStop
local lblText, barGauge, lblDelta, btnStop, dlgGauge
local function doFocus() -- Bring the Progress Bar window into Focus
dlgGauge.BringFront="YES" -- If used too often, inhibits other windows scroll bars, etc
end -- local function doFocus
local function doUpdate() -- Update the Progress Gauge and the Delta % with clock
barGauge.Value = intVal
lblDelta.Title = string.format("%4d %% %s ",math.floor(intPercent),strClock)
end -- local function doUpdate
local function doReset() -- Reset all dialogue variables and Update display
intVal = 0 -- Current value of Progress Bar
intPercent= 0.01 -- Percentage of progress
intStart = os.time() -- Start time of progress
intDelta = 0 -- Delta time of progress
intScale = math.ceil( intMax / 1000 ) -- Scale of percentage per second of progress (initial guess is corrected in Step function)
strClock = "00 : 00 : 00" -- Clock delta time display
isBarStop = false -- Stop button pressed signal
doUpdate()
doFocus()
end -- local function doReset
function fh.Start(strTitle,intMaximum) -- Create & start Progress Bar window
if not dlgGauge then
strTitle = strTitle or "" -- Dialogue and button title
intMax = intMaximum or 100 -- Maximun range of Progress Bar, default is 100
local strSize = tostring( math.max( 100, string.len(" Stop "..strTitle) * 8 ) ).."x30" -- Adjust Stop button size to Title
lblText = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Progress Message"; }
barGauge = iup.progressbar { RasterSize="400x30"; Value=0; Max=intMax; Tip="Progress Bar"; }
lblDelta = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Percentage and Elapsed Time"; }
btnStop = iup.button { Title=" Stop "..strTitle; RasterSize=strSize; FgColor=strStop; Tip="Stop Progress Button"; action=function() isBarStop = true end; } -- Signal Stop button pressed return iup.CLOSE -- Often caused main GUI to close !!!
dlgGauge = iup.dialog { Title=strTitle.." Progress "; Font=strFont; FgColor=strBody; Background=strBack; DialogFrame="YES"; -- Remove Windows minimize/maximize menu
iup.vbox{ Alignment="ACENTER"; Gap="10"; Margin="10x10";
lblText;
barGauge;
lblDelta;
btnStop;
};
move_cb = function(self,x,y) tblBars.X = x tblBars.Y = y end;
close_cb = btnStop.action; -- Windows Close button = Stop button
}
if type(tblBars.GUI) == "table"
and type(tblBars.GUI.ShowDialogue) == "function" then
dlgGauge.move_cb = nil -- Use GUI library to show & move window
tblBars.GUI.ShowDialogue("Bars",dlgGauge,btnStop,"showxy")
else
if fhGetAppVersion() > 6 then -- Window centres on FH parent -- V3.1
iup.SetAttribute(dlgGauge,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
dlgGauge:showxy(intPosX,intPosY) -- Show the Progress Bar window
end
doReset() -- Reset the Progress Bar display
end
end -- function Start
function fh.Message(strText) -- Show the Progress Bar message
if dlgGauge then lblText.Title = strText end
end -- function Message
function fh.Step(intStep) -- Step the Progress Bar forward
if dlgGauge then
intVal = intVal + ( intStep or 1 ) -- Default step is 1
local intNew = math.ceil( intVal / intMax * 100 * intScale ) / intScale
if intPercent ~= intNew then -- Update progress once per percent or per second, whichever is smaller
intPercent = math.max( 0.1, intNew ) -- Ensure percentage is greater than zero
if intVal > intMax then intVal = intMax intPercent = 100 end -- Ensure values do not exceed maximum
intNew = os.difftime(os.time(),intStart)
if intDelta < intNew then -- Update clock of elapsed time
intDelta = intNew
intScale = math.ceil( intDelta / intPercent ) -- Scale of seconds per percentage step
local intHour = math.floor( intDelta / 3600 )
local intMins = math.floor( intDelta / 60 - intHour * 60 )
local intSecs = intDelta - intMins * 60 - intHour * 3600
strClock = string.format("%02d : %02d : %02d",intHour,intMins,intSecs)
end
doUpdate() -- Update the Progress Bar display
end
iup.LoopStep()
end
end -- function Step
function fh.Focus() -- Bring the Progress Bar window to front
if dlgGauge then doFocus() end
end -- function Focus
function fh.Reset() -- Reset the Progress Bar display
if dlgGauge then doReset() end
end -- function Reset
function fh.Stop() -- Check if Stop button pressed
iup.LoopStep()
return isBarStop
end -- function Stop
function fh.Close() -- Close the Progress Bar window
isBarStop = false
if dlgGauge then dlgGauge:destroy() dlgGauge = nil end
end -- function Close
function fh.Setup(tblSetup) -- Setup optional table of external attributes
if tblSetup then
tblBars = tblSetup
strBack = tblBars.Back or strBack -- Background colour
strBody = tblBars.Body or strBody -- Body text colour
strFont = tblBars.Font or strFont -- Font dialogue
strStop = tblBars.Stop or strStop -- Stop button colour
intPosX = tblBars.X or intPosX -- Window position
intPosY = tblBars.Y or intPosY
end
end -- function Setup
return fh
end -- local function progbar_v3
local progbar = progbar_v3() -- To access FH progress bars module
--[[
@Module: +fh+iup_gui_v3
@Author: Mike Tate
@Version: 4.4
@LastUpdated: 29 Jan 2026
@Description: Graphical User Interface Library Module
@V4.4: Introduce use of NATIVEPARENT and CENTERPARENT to centre on parent window by default; Ensure not off screen; Monitors with -ve X;
@V4.3: Added memo options to CheckVersionInStore;
@V4.2: Skip if standalone GEDCOM in fh.SaveSettings() and getDataFiles();
@V4.1: CheckVersionInStore() save & retrieve latest version in file; Remove old wiki Help features;
@V4.0: Cater for full UTF-8 filenames;
@V3.9: ShowDialogue() popup closure fhSleep() added; CheckVersionInStore() at monthly intervals;
@V3.8: Function Prototype Closure version.
@V3.7: AssignAttributes(tblControls) now allows any string attribute to invoke a function.
@V3.6: anyMemoDialogue() sets TopMost attribute.
@V3.5: Replace IsNormalWindow(iupDialog) with SetWindowCoord(tblName) and update CheckWindowPosition(tblName) to prevent negative values freezing main dialog.
@V3.4: Use general.MakeFolder() to ensure key folders exist, add Get/PutRegKey(), check Registry IE Shell Version in HelpDialogue(), better error handling in LoadSettings().
@V3.3: LoadFolder() and SaveFolder() use global folder as default for local folder to improve synch across PC.
@V3.2: Load & Save settings now use a single clipboard so Local PC settings are preserved across synchronised PC.
@V3.1: IUP 3.11.2 iup.GetGlobal("VERSION") to top, HelpDialogue conditional ExpandChildren="YES/NO", RefreshDialogue uses NaturalSize, SetUtf8Mode(), Load/SaveFolder(), etc
@V3.0: ShowDialogue "dialog" mode for Memo, new DestroyDialogue, NewHelpDialogue tblAttr for Font, AssignAttributes intSkip, CustomDialogue iup.CENTERPARENT+, IUP Workaround, BalloonToggle, Initialise test Plugin file exists.
@V2.0: Support for Plugin Data scope, new FontDialogue, RefreshDialogue, AssignAttributes, httpRequest handler, keep "dialog" mode.
@V1.0: Initial version.
]]
local function iup_gui_v3()
local fh = {} -- Local environment table
require "iuplua" -- To access GUI window builder
require "iupluacontrols" -- To access GUI window controls
require "lfs" -- To access LUA filing system
require "iupluaole" -- To access OLE subsystem
require "luacom" -- To access COM subsystem
iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28
local iupVersion = iup.GetGlobal("VERSION") -- Obtain IUP module version
-- "iuplua" Omitted Constants Workaround --
iup.TOP = iup.LEFT
iup.BOTTOM = iup.RIGHT
iup.RED = iup.RGB(1,0,0)
iup.GREEN = iup.RGB(0,1,0)
iup.BLUE = iup.RGB(0,0,1)
iup.BLACK = iup.RGB(0,0,0)
iup.WHITE = iup.RGB(1,1,1)
iup.YELLOW = iup.RGB(1,1,0)
-- Shared Interface Attributes & Functions --
fh.Version = " " -- Plugin Version
fh.History = fh.Version -- Version History
fh.Red = "255 0 0" -- Color attributes (must exclude leading zeros & spaces to allow value comparisons)
fh.Maroon = "128 0 0"
fh.Amber = "250 160 0"
fh.Orange = "255 165 0"
fh.Yellow = "255 255 0"
fh.Olive = "128 128 0"
fh.Lime = "0 255 0"
fh.Green = "0 128 0"
fh.Cyan = "0 255 255"
fh.Teal = "0 128 128"
fh.Blue = "0 0 255"
fh.Navy = "0 0 128"
fh.Magenta = "255 0 255"
fh.Purple = "128 0 128"
fh.Black = "0 0 0"
fh.Gray = "128 128 128"
fh.Silver = "192 192 192"
fh.Smoke = "240 240 240"
fh.White = "255 255 255"
fh.Risk = fh.Red -- Risk colour for hazardous controls such as Close/Delete buttons
fh.Warn = fh.Magenta -- Warn colour for caution controls and warnings
fh.Safe = fh.Green -- Safe colour for active controls such as most buttons
fh.Info = fh.Black -- Info colour for text controls such as labels/tabs
fh.Head = fh.Black -- Head colour for headings
fh.Body = fh.Black -- Body colour for body text
fh.Back = fh.White -- Background colour for all windows
fh.Gap = "8" -- Layout attributes Gap was "10"
fh.Border = "8x8" -- was BigMargin="10x10"
fh.Margin = "1x1" -- was MinMargin
fh.Balloon = "NO" -- Tooltip balloon mode
fh.FontSet = 0 -- Legacy GUI font set assigned by FontAssignment but used globally
fh.FontHead = ""
fh.FontBody = ""
local GUI = { } -- Sub-table for GUI Dialogue attributes to allow any "Name"
--[[
GUI.Name table of dialogue attributes, where Name is Font, Main, Memo, Bars, etc
GUI.Name.CoordX x co-ordinate ( Loaded & Saved by default )
GUI.Name.CoordY y co-ordinate ( Loaded & Saved by default )
GUI.Name.Dialog dialogue handle
GUI.Name.Focus focus button handle
GUI.Name.Frame dialogframe mode, "normal" = dialogframe="NO" else "YES", "showxy" = showxy(), "popup" or "keep" = popup(), default is "normal & showxy"
GUI.Name.Height height
GUI.Name.Raster rastersize ( Loaded & Saved by default )
GUI.Name.Width width
GUI.Name.Back ProgressBar background colour
GUI.Name.Body ProgressBar body text colour
GUI.Name.Font ProgressBar font style
GUI.Name.Stop ProgressBar Stop button colour
GUI.Name.GUI Module table usable by other modules e.g. progbar.Setup
--]]
-- tblScrn[1] = origin x, tblScrn[2] = origin y, tblScrn[3] = width, tblScrn[4] = height
local tblScrn = stringx.splitnumbers(iup.GetGlobal("VIRTUALSCREEN")) -- Used by CustomDialogue() and CheckWindowPosition() and ShowDialogue() below
local intMinX = tblScrn[1]
local intMinY = tblScrn[2] -- V4.4
local intMaxW = tblScrn[3]
local intMaxH = tblScrn[4]
function fh.BalloonToggle() -- Toggle tooltips Balloon mode
local tblToggle = { YES="NO"; NO="YES"; }
fh.Balloon = tblToggle[fh.Balloon]
fh.SaveSettings()
end -- function BalloonToggle
iup.SetGlobal("UTF8MODE","NO")
iup.SetGlobal("UTF8MODE_FILE","NO") -- V4.0
function fh.SetUtf8Mode() -- Set IUP into UTF-8 mode
if iupVersion == "3.5" or stringx.encoding() == "ANSI" then return false end
iup.SetGlobal("UTF8MODE","YES")
iup.SetGlobal("UTF8MODE_FILE","YES") -- V4.0
return true
end -- function SetUtf8Mode
local function tblOfNames(...) -- Get table of dialogue Names including "Font","Main" by default -- V4.4
local arg = {...}
local tblNames = {"Font";"Main";}
for intName, strName in ipairs(arg) do
if type(strName) == "string"
and strName ~= "Font"
and strName ~= "Main" then
table.insert(tblNames,strName)
end
end
return tblNames
end -- local function tblOfNames
local function tblNameFor(strName) -- Get table of parameters for chosen dialogue Name
strName = tostring(strName)
if not GUI[strName] then -- Need new table with default minimum & raster size, and X & Y co-ordinates
GUI[strName] = { }
local tblName = GUI[strName]
tblName.Raster = "x"
tblName.CoordX = iup.CENTERPARENT
tblName.CoordY = iup.CENTERPARENT
end
return GUI[strName]
end -- local function tblNameFor
local function intDimension(intMin,intVal,intMax) -- Return a number bounded by intMin and intMax
if not intVal then return 0 end -- Except if no value then return 0
intVal = tonumber(intVal) or (intMin+intMax)/2
return math.max(intMin,math.min(intVal,intMax))
end -- local function intDimension
function fh.CustomDialogue(strName,strRas,intX,intY) -- GUI custom window raster size, and X & Y co-ordinates
-- strRas nil = old size, "x" or "0x0" = min size, "999x999" = new size
-- intX/Y nil = central, "99" = co-ordinate position
local tblName = tblNameFor(strName)
local tblSize = {}
local intWide = 0
local intHigh = 0
strRas = strRas or tblName.Raster
if strRas then -- Ensure raster size is between minimum and screen size
tblSize = stringx.splitnumbers(strRas)
intWide = intDimension(intWide,tblSize[1],intMaxW)
intHigh = intDimension(intHigh,tblSize[2],intMaxH)
strRas = tostring(intWide.."x"..intHigh)
end
if intX and intX < iup.CENTERPARENT then
intX = intDimension(intMinX,intX,intMaxW-intWide) -- Ensure X co-ordinate positions window on screen -- V4.4
end
if intY and intY < iup.CENTERPARENT then
intY = intDimension(intMinY,intY,intMaxH-intHigh) -- Ensure Y co-ordinate positions window on screen -- V4.4
end
tblName.Raster = strRas or "x"
tblName.CoordX = tonumber(intX) or iup.CENTERPARENT -- V4.4
tblName.CoordY = tonumber(intY) or iup.CENTERPARENT -- V4.4
end -- function CustomDialogue
function fh.DefaultDialogue(...) -- GUI default window minimum & raster size, and X & Y co-ordinates
for intName, strName in ipairs(tblOfNames(...)) do
fh.CustomDialogue(strName)
end
end -- function DefaultDialogue
function fh.DialogueAttributes(strName) -- Provide named Dialogue Attributes
local tblName = tblNameFor(strName) -- tblName.Dialog = dialog handle, so any other attributes could be retrieved
local tblSize = stringx.splitnumbers(tblName.Raster or "x") -- Split Raster Size into width=tblSize[1] and height=tblSize[2]
tblName.Width = tblSize[1]
tblName.Height= tblSize[2]
tblName.Back = fh.Back -- Following only needed for NewProgressBar
tblName.Body = fh.Body
tblName.Font = fh.FontBody
tblName.Stop = fh.Risk
tblName.GUI = fh -- Module table
return tblName
end -- function DialogueAttributes
local strDefaultScope = "Project" -- Default scope for Load/Save data is per Project/User/Machine as set by PluginDataScope()
local tblClipProj = { }
local tblClipUser = { } -- Clipboards of sticky data for each Plugin Data scope -- V3.2
local tblClipMach = { }
local function doLoadData(strParam,strDefault,strScope) -- Load sticky data for Plugin Data scope
strScope = tostring(strScope or strDefaultScope):lower()
local tblClipData = tblClipProj
if strScope:match("user") then tblClipData = tblClipUser
elseif strScope:match("mach") then tblClipData = tblClipMach
end
return tblClipData[strParam] or strDefault
end -- local function doLoadData
function fh.LoadGlobal(strParam,strDefault,strScope) -- Load Global Parameter for all PC
return doLoadData(strParam,strDefault,strScope)
end -- function LoadGlobal
function fh.LoadLocal(strParam,strDefault,strScope) -- Load Local Parameter for this PC
return doLoadData(fh.ComputerName.."-"..strParam,strDefault,strScope)
end -- function LoadLocal
local function doLoadFolder(strFolder) -- Use relative paths to let Paths change -- V3.3
strFolder = strFolder:gsub("^FhDataPath",function() return fh.FhDataPath end) -- Full path to .fh_data folder
strFolder = strFolder:gsub("^PublicPath",function() return fh.PublicPath end) -- Full path to Public folder
strFolder = strFolder:gsub("^FhProjPath",function() return fh.FhProjPath end) -- Full path to project folder
return strFolder
end -- local function doLoadFolder
function fh.LoadFolder(strParam,strDefault,strScope) -- Load Folder Parameter for this PC -- V3.3
local strFolder = doLoadFolder(fh.LoadLocal(strParam,"",strScope))
if not general.FlgFolderExists(strFolder) then -- If no local folder try global folder
strFolder = doLoadFolder(fh.LoadGlobal(strParam,strDefault,strScope))
end
return strFolder
end -- function LoadFolder
function fh.LoadDialogue(...) -- Load Dialogue Parameters for "Font","Main" by default
for intName, strName in ipairs(tblOfNames(...)) do
local tblName = tblNameFor(strName)
tblName.Raster = tostring(fh.LoadLocal(strName.."R",tblName.Raster))
tblName.CoordX = tonumber(fh.LoadLocal(strName.."X",tblName.CoordX))
tblName.CoordY = tonumber(fh.LoadLocal(strName.."Y",tblName.CoordY))
fh.CheckWindowPosition(tblName)
end
end -- function LoadDialogue
function fh.LoadSettings(...) -- Load Sticky Settings from File
for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do
strFileName = fh[strFileName]
if general.FlgFileExists(strFileName) then -- Load Settings File in table lines with key & val fields
local tblField = {}
local strClip = general.StrLoadFromFile(strFileName) -- V4.0
for strLine in strClip:gmatch("[^\r\n]+") do -- V4.0
if #tblField == 0
and strLine:match("^return {") -- Unless entire Sticky Data table was saved
and type(table.load) == "function" then
local tblClip, strErr = table.load(strFileName) -- Load Settings File table
if strErr then error(strErr.."\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.") end
for i,j in pairs (tblClip) do
tblClipData[i] = tblClip[i]
end
break
end
tblField = stringx.split(strLine,"=")
if tblField[1] then tblClipData[tblField[1]] = tblField[2] end
end
else
for i,j in pairs (tblClipData) do
tblClipData[i] = nil -- Restore defaults and clear any junk -- V4.0
end
end
end
fh.Safe = tostring(fh.LoadGlobal("SafeColor",fh.Safe))
fh.Warn = tostring(fh.LoadGlobal("WarnColor",fh.Warn))
fh.Risk = tostring(fh.LoadGlobal("RiskColor",fh.Risk))
fh.Head = tostring(fh.LoadGlobal("HeadColor",fh.Head))
fh.Body = tostring(fh.LoadGlobal("BodyColor",fh.Body))
fh.FontHead= tostring(fh.LoadGlobal("FontHead" ,fh.FontHead))
fh.FontBody= tostring(fh.LoadGlobal("FontBody" ,fh.FontBody))
fh.History = tostring(fh.LoadGlobal("History" ,fh.History))
fh.Balloon = tostring(fh.LoadGlobal("Balloon" ,fh.Balloon, "Machine"))
fh.LoadDialogue(...)
end -- function LoadSettings
local function doSaveData(strParam,anyValue,strScope) -- Save sticky data for Plugin Data scope
strScope = tostring(strScope or strDefaultScope):lower()
local tblClipData = tblClipProj
if strScope:match("user") then tblClipData = tblClipUser
elseif strScope:match("mach") then tblClipData = tblClipMach
end
tblClipData[strParam] = anyValue
end -- local function doSaveData
function fh.SaveGlobal(strParam,anyValue,strScope) -- Save Global Parameter for all PC
doSaveData(strParam,anyValue,strScope)
end -- function SaveGlobal
function fh.SaveLocal(strParam,anyValue,strScope) -- Save Local Parameter for this PC
doSaveData(fh.ComputerName.."-"..strParam,anyValue,strScope)
end -- function SaveLocal
function fh.SaveFolder(strParam,strFolder,strScope) -- Save Folder Parameter for this PC
strFolder = stringx.replace(strFolder,fh.FhDataPath,"FhDataPath") -- Full path to .fh_data folder
strFolder = stringx.replace(strFolder,fh.PublicPath,"PublicPath") -- Full path to Public folder
strFolder = stringx.replace(strFolder,fh.FhProjPath,"FhProjPath") -- Full path to project folder
fh.SaveGlobal(strParam,strFolder,strScope) -- V3.3
fh.SaveLocal(strParam,strFolder,strScope) -- Uses relative paths to let Paths change
end -- function SaveFolder
function fh.SaveDialogue(...) -- Save Dialogue Parameters for "Font","Main" by default
for intName, strName in ipairs(tblOfNames(...)) do
local tblName = tblNameFor(strName)
fh.SaveLocal(strName.."R",tblName.Raster)
fh.SaveLocal(strName.."X",tblName.CoordX)
fh.SaveLocal(strName.."Y",tblName.CoordY)
end
end -- function SaveDialogue
function fh.SaveSettings(...) -- Save Sticky Settings to File
fh.SaveDialogue(...)
fh.SaveGlobal("SafeColor",fh.Safe)
fh.SaveGlobal("WarnColor",fh.Warn)
fh.SaveGlobal("RiskColor",fh.Risk)
fh.SaveGlobal("HeadColor",fh.Head)
fh.SaveGlobal("BodyColor",fh.Body)
fh.SaveGlobal("FontHead" ,fh.FontHead)
fh.SaveGlobal("FontBody" ,fh.FontBody)
fh.SaveGlobal("History" ,fh.History)
fh.SaveGlobal("Balloon" ,fh.Balloon, "Machine")
for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do
for i,j in pairs (tblClipData) do -- Check if table has any entries
strFileName = fh[strFileName]
if #strFileName > 0 then -- Skip for standalone GEDCOM -- V4.2
if type(table.save) == "function" then -- Save entire Settings File table per Project/User/Machine
table.save(tblClipData,strFileName)
else
local tblClip = {}
for strKey,strVal in pairs(tblClipData) do -- Else save Settings File lines with key & val fields -- V4.0
table.insert(tblClip,strKey.."="..strVal.."\n") -- V4.0
end
local strClip = table.concat(tblClip,"\n") -- V4.0
if not general.SaveStringToFile(strClip,strFileName) then
error("\nSettings file not saved successfully.\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.")
end
end
end
break
end
end
end -- function SaveSettings
function fh.CheckWindowPosition(tblName) -- Ensure dialogue window coordinates are on Screen
local tblSize = stringx.splitnumbers(tblName.Raster or "600x400","x") -- Get window dimensions from the previous use of plugin -- V4.4
local intWinW = tblSize[1]
local intWinH = tblSize[2]
if tonumber(tblName.CoordX) == nil
or tonumber(tblName.CoordY) == nil
or tonumber(tblName.CoordX) < intMinX -- V4.4 -- V3.5
or tonumber(tblName.CoordY) < intMinY -- V4.4 -- V3.5
or tonumber(tblName.CoordX) + intWinW > intMaxW -- V4.4
or tonumber(tblName.CoordY) + intWinH > intMaxH then -- V4.4
tblName.CoordX = iup.CENTERPARENT -- V4.4
tblName.CoordY = iup.CENTERPARENT -- V4.4
end
end -- function CheckWindowPosition
function fh.SetWindowCoord(tblName) -- Set the Window coordinates if not Maximised or Minimised -- V3.5
-- tblPosn[1] = origin x, tblPosn[2] = origin y, tblPosn[3] = width, tblPosn[4] = height
local tblPosn = stringx.splitnumbers(tblName.Dialog.ScreenPosition)
local intPosX = tblPosn[1] or -1
local intPosY = tblPosn[2] or -1
if intPosX < 0 and intPosY < 0 then -- If origin is negative (-8, -8 = Maximised, -3200, -3200 = Minimised)
return false -- then is Maximised or Minimised
end
tblName.CoordX = intPosX -- Otherwise set the Window coordinates
tblName.CoordY = intPosY
return true
end -- function SetWindowCoord
function fh.ShowDialogue(strName,iupDialog,btnFocus,strFrame) -- Set standard frame attributes and display dialogue window
local tblName = tblNameFor(strName)
iupDialog = iupDialog or tblName.Dialog -- Retrieve previous parameters if needed
btnFocus = btnFocus or tblName.Focus
strFrame = strFrame or tblName.Frame
strFrame = strFrame or "show norm" -- Default frame mode is dialog:showxy(X,Y) with DialogFrame="NO" ("normal" to vary size, otherwise fixed size)
strFrame = strFrame:lower() -- Other modes are "show", "popup" & "keep" with DialogFrame="YES", or with "normal" for DialogFrame="NO" ("show" for active windows, "popup"/"keep" for modal windows)
if strFrame:gsub("%s-%a-map%a*[%s%p]*","") == "" then -- May be prefixed with "map" mode to just map dialogue initially, also may be suffixed with "dialog" to inhibit iup.MainLoop() to allow progress messages
strFrame = "map show norm" -- If only "map" mode then default to "map show norm"
end
if type(iupDialog) == "userdata" then
tblName.Dialog = iupDialog
tblName.Focus = btnFocus -- Preserve parameters
tblName.Frame = strFrame
iupDialog.Background = fh.Back -- Background colour
iupDialog.Shrink = "YES" -- Sometimes needed to shrink controls to raster size
if type(btnFocus) == "userdata" then -- Set button as focus for Esc and Enter keys
iupDialog.StartFocus = iupDialog.StartFocus or btnFocus
iupDialog.DefaultEsc = iupDialog.DefaultEsc or btnFocus
iupDialog.DefaultEnter = iupDialog.DefaultEnter or btnFocus
end
if tblName.CoordX == iup.CENTERPARENT
or tblName.CoordY == iup.CENTERPARENT then -- When centred on parent minimise this window size -- V4.4
tblName.Raster= iupDialog.MinSize
else
iupDialog.MinSize = "x" -- Minimum size (default "x" becomes nil)
iupDialog.RasterSize = tblName.Raster or "x" -- Raster size (default "x" becomes nil)
end
iupDialog.MaxSize = intMaxW.."x"..intMaxH -- Maximum size is virtual screen size
if strFrame:match("norm") then -- DialogFrame mode is "NO" by default for variable size window
if strFrame:match("pop") or strFrame:match("keep") then
iupDialog.MinBox = "NO" -- For "popup" and "keep" hide Minimize and Maximize icons
iupDialog.MaxBox = "NO"
else
strFrame = strFrame.." show" -- If not "popup" nor "keep" then use "showxy" mode
end
else
iupDialog.DialogFrame = "YES" -- Define DialogFrame mode for fixed size window
end
iupDialog.close_cb = iupDialog.close_cb or function() return iup.CLOSE end -- Define default window X close, move, and resize actions
iupDialog.move_cb = iupDialog.move_cb or function(self) if iup.MainLoopLevel() > 0 then fh.SetWindowCoord(tblName) end end -- V3.5 -- V4.4
iupDialog.resize_cb = iupDialog.resize_cb or function(self) if iup.MainLoopLevel() > 0 and fh.SetWindowCoord(tblName) then tblName.Raster=self.RasterSize end end -- V3.5 -- V4.4
fh.RefreshDialogue(strName) -- Refresh to set Natural Size as Minimum Size
if strName == "Main" then
if fhGetAppVersion() > 6 then -- Main window centres on FH parent -- V4.4
iup.SetAttribute(iupDialog,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
else
local tblMain = tblNameFor("Main") -- Others popup centrally in Main -- V4.4
local iupMain = tblMain.Dialog
if iupMain then -- Centre based on size of windows -- V1.4
local arrName = stringx.splitnumbers(tblName.Raster or "0x0")
local arrMain = stringx.splitnumbers(tblMain.Raster or arrName[1].."x"..arrName[2])
tblName.CoordX = iupMain.X + math.floor( (arrMain[1] - arrName[1]) / 2 )
tblName.CoordY = iupMain.Y + math.floor( (arrMain[2] - arrName[2]) / 2 )
elseif fhGetAppVersion() > 6 then -- This window centres on FH parent -- V4.4
iup.SetAttribute(iupDialog,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
end
if strFrame:match("map") then -- Only dialogue mapping is required
iupDialog:map()
tblName.Frame = strFrame:gsub("%s-%a-map%a*[%s%p]*","") -- Remove "map" from frame mode ready for subsequent call
return
end
if iup.MainLoopLevel() == 0 -- Called from outside Main GUI, so must use showxy() and not popup()
or strFrame:match("dialog")
or strFrame:match("sho") then -- Use showxy() to dispay dialogue window for "showxy" or "dialog" mode
iupDialog:showxy(tblName.CoordX,tblName.CoordY)
if not strFrame:match("dialog") -- Inhibit MainLoop if "dialog" mode -- V4.1
and iup.MainLoopLevel() == 0 then iup.MainLoop() end
else
iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to display dialogue window for "popup" or "keep" modes
fhSleep(200,150) -- Sometimes needed to prevent MainLoop() closure! -- V3.9
end
if not strFrame:match("dialog") and strFrame:match("pop") then
tblName.Dialog = nil -- When popup closed, clear key parameters, but not for "keep" mode
tblName.Raster = nil
tblName.CoordX = nil -- iup.CENTERPARENT
tblName.CoordY = nil -- iup.CENTERPARENT
elseif tblName.CoordX ~= iup.CENTERPARENT -- V4.4
and tblName.CoordY ~= iup.CENTERPARENT then
fh.SetWindowCoord(tblName) -- Set Window coordinate pixel values -- V3.5
end
end
end -- function ShowDialogue
function fh.DestroyDialogue(strName) -- Destroy existing dialogue
local tblName = tblNameFor(strName)
if tblName then
local iupDialog = tblName.Dialog
if type(iupDialog) == "userdata" then
iupDialog:destroy()
tblName.Dialog = nil -- Prevent future misuse of handle -- 22 Jul 2014
end
end
end -- function DestroyDialogue
local function strDialogueArgs(strArgA,strArgB,comp) -- Compare two argument pairs and return matching pair
local tblArgA = stringx.splitnumbers(strArgA)
local tblArgB = stringx.splitnumbers(strArgB)
local strArgX = tostring(comp(tblArgA[1] or 100,tblArgB[1] or 100))
local strArgY = tostring(comp(tblArgA[2] or 100,tblArgB[2] or 100))
return strArgX.."x"..strArgY
end -- local function strDialogueArgs
function fh.RefreshDialogue(strName) -- Refresh dialogue window size after Font change, etc
local tblName = tblNameFor(strName)
local iupDialog = tblName.Dialog -- Retrieve the dialogue handle
if type(iupDialog) == "userdata" then
iupDialog.Size = iup.NULL
iupDialog.MinSize = iup.NULL -- V3.1
iup.Refresh(iupDialog) -- Refresh window to Natural Size and set as Minimum Size
if not iupDialog.RasterSize then
iupDialog:map()
iup.Refresh(iupDialog)
end
local strSize = iupDialog.NaturalSize or iupDialog.RasterSize -- IUP 3.5 NaturalSize = nil, IUP 3.11 needs NaturalSize -- V3.1
iupDialog.MinSize = strDialogueArgs(iupDialog.MaxSize,strSize,math.min) -- Set Minimum Size to smaller of Maximm Size or Natural/Raster Size -- V3.1
iupDialog.RasterSize = strDialogueArgs(tblName.Raster,strSize,math.max) -- Set Current Size to larger of Current Size or Natural/Raster Size -- V3.1
iup.Refresh(iupDialog)
tblName.Raster = iupDialog.RasterSize
if iupDialog.Visible == "YES" then -- Ensure visible dialogue origin is on screen
tblName.CoordX = math.max(tblName.CoordX,10)
tblName.CoordY = math.max(tblName.CoordY,10) -- Set both coordinates to larger of current value or 10 pixels
if iupDialog.Modal then -- V3.8
if iupDialog.Modal == "NO" then
iupDialog.ZOrder = "BOTTOM" -- Ensure dialogue is subservient to any popup
iupDialog:showxy(tblName.CoordX,tblName.CoordY) -- Use showxy() to reposition main window
else
iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to reposition modal window
end
end
else
iupDialog.BringFront="YES"
end
end
end -- function RefreshDialogue
function fh.AssignAttributes(tblControls) -- Assign the attributes of all controls supplied
local anyFunction = nil
for iupName, tblAttr in pairs ( tblControls or {} ) do
if type(iupName) == "userdata" and type(tblAttr) == "table" then -- Loop through each iup control
local intSkip = 0 -- Skip counter for attributes same for all controls
for intAttr, anyName in ipairs ( tblControls[1] or {} ) do -- Loop through each iup attribute
local strName = nil
local strAttr = nil
local strType = type(anyName)
if strType == "string" then -- Attribute is different for each control in tblControls
strName = anyName
strAttr = tblAttr[intAttr-intSkip]
elseif strType == "table" then -- Attribute is same for all controls as per tblControls[1]
intSkip = intSkip + 1
strName = anyName[1]
strAttr = anyName[2]
elseif strType == "function" then
intSkip = intSkip + 1
anyFunction = anyName
break
end
if type(strName) == "string" and ( type(strAttr) == "string" or type(strAttr) == "function" ) then
local anyRawGet = rawget(fh,strAttr) -- Use rawget() to stop require("pl.strict") complaining
if type(anyRawGet) == "string" then
strAttr = anyRawGet -- Use internal module attribute such as Head or FontBody
elseif type(iupName[strName]) == "string"
and type(strAttr) == "function" then -- Allow string attributes to invoke a function -- V3.7
strAttr = strAttr()
end
iupName[strName] = strAttr -- Assign attribute to control
end
end
end
end
if anyFunction then anyFunction() end -- Perform any control assignment function
end -- function AssignAttributes
-- Font Dialogue Attributes and Functions --
fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default font for Body and Head text
fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold ")
function fh.FontDialogue(tblAttr,strName) -- GUI Font Face & Style Dialogue
tblAttr = tblAttr or {}
strName = strName or "Main"
local isFontChosen = false
local btnFontHead = iup.button { Title="Choose Headings Font and default Colour"; }
local btnFontBody = iup.button { Title="Choose Body text Font and default Colour"; }
local btnCol_Safe = iup.button { Title=" Safe Colour "; }
local btnCol_Warn = iup.button { Title=" Warning Colour "; }
local btnCol_Risk = iup.button { Title=" Risky Colour "; }
local btnDefault = iup.button { Title=" Default Fonts "; }
local btnMinimum = iup.button { Title=" Minimum Size "; }
local btnDestroy = iup.button { Title=" Close Dialogue "; }
local frmSetFonts = iup.frame { Title=" Set Window Fonts & Colours ";
iup.vbox { Alignment="ACENTER"; Margin=fh.Margin; Homogeneous="YES";
btnFontHead;
btnFontBody;
iup.hbox { btnCol_Safe; btnCol_Warn; btnCol_Risk; Homogeneous="YES"; };
iup.hbox { btnDefault ; btnMinimum ; btnDestroy ; Homogeneous="YES"; };
} -- iup.vbox end
} -- iup.frame end
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local dialogFont = iup.dialog { Title=" Set Window Fonts & Colours "; Gap=fh.Gap; Margin=fh.Border; frmSetFonts; }
local tblButtons = { }
local function setDialogues() -- Refresh the Main dialogue -- V4.4
fh.AssignAttributes(tblAttr) -- Assign parent dialogue attributes
fh.RefreshDialogue(strName) -- Refresh parent window size & position and bring infront of other window
fh.RefreshDialogue("Font") -- Refresh Font window size & position and bring infront of parent window
end -- local function setDialogues
local function getFont(strColor) -- Set font button function
local strTitle = " Choose font style & default colour for "..strColor:gsub("Head","Heading").." text "
local strValue = "Font"..strColor -- The font codes below are not recognised by iupFontDlg and result in empty font face!
local strFont = rawget(fh,strValue):gsub(" Black,",","):gsub(" Light, Bold",","):gsub(" Extra Bold,",","):gsub(" Semibold,",",")
local iupFontDlg = iup.fontdlg { Title=strTitle; Color=rawget(fh,strColor); Value=strFont; }
iupFontDlg:popup() -- Popup predefined font dialogue
if iupFontDlg.Status == "1" then
if iupFontDlg.Value:match("^,") then -- Font face missing so revert to original font
iupFontDlg.Value = rawget(fh,strValue)
end
fh[strColor] = iupFontDlg.Color -- Set Head or Body color attribute
fh[strValue] = iupFontDlg.Value -- Set FontHead or FontBody font style
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
setDialogues()
isFontChosen = true
end
end -- local function getFont
local function getColor(strColor) -- Set colour button function
local strTitle = " Choose colour for "..strColor:gsub("Warn","Warning"):gsub("Risk","Risky").." button & message text "
local iupColorDlg = iup.colordlg { Title=strTitle; Value=rawget(fh,strColor); ShowColorTable="YES"; }
iupColorDlg.DialogFrame="YES"
iupColorDlg:popup() -- Popup predefined color dialogue fixed size window
if iupColorDlg.Status == "1" then
fh[strColor] = iupColorDlg.Value -- Set Safe or Warn or Risk color attribute
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
setDialogues()
isFontChosen = true
end
end -- local function getColor
local function setDefault() -- Action for Default Fonts button
fh.Safe = fh.Green
fh.Warn = fh.Magenta
fh.Risk = fh.Red -- Set default colours
fh.Body = fh.Black
fh.Head = fh.Black
fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default fonts for Body and Head text
fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold")
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
setDialogues()
isFontChosen = true
end -- local function setDefault
local function setMinimum() -- Action for Minimum Size button
local tblName = tblNameFor(strName)
local iupDialog = tblName.Dialog -- Retrieve the parent dialogue handle
if type(iupDialog) == "userdata" then
tblName.Raster = "10x10" -- Refresh parent window to Minimum Size & adjust position
fh.RefreshDialogue(strName)
end
local tblFont = tblNameFor("Font")
tblFont.Raster = "10x10" -- Refresh Font window to Minimum Size & adjust position
fh.RefreshDialogue("Font")
end -- local function setMinimum
tblButtons = { { "Font" ; "FgColor" ; "Tip" ; "action" ; {"TipBalloon";"Balloon";} ; {"Expand";"YES";} ; };
[btnFontHead] = { "FontHead"; "Head"; "Choose the Heading text Font Face, Style, Size, Effects, and default Colour"; function() getFont("Head") end; };
[btnFontBody] = { "FontBody"; "Body"; "Choose the Body text Font Face, Style, Size, Effects, and default Colour" ; function() getFont("Body") end; };
[btnCol_Safe] = { "FontBody"; "Safe"; "Choose the colour for Safe operations" ; function() getColor("Safe") end; };
[btnCol_Warn] = { "FontBody"; "Warn"; "Choose the colour for Warning operations"; function() getColor("Warn") end; };
[btnCol_Risk] = { "FontBody"; "Risk"; "Choose the colour for Risky operations" ; function() getColor("Risk") end; };
[btnDefault ] = { "FontBody"; "Safe"; "Restore default Fonts and Colours"; function() setDefault() end; };
[btnMinimum ] = { "FontBody"; "Safe"; "Reduce window to its minimum size"; function() setMinimum() end; };
[btnDestroy ] = { "FontBody"; "Risk"; "Close this dialogue "; function() return iup.CLOSE end; };
[frmSetFonts] = { "FontHead"; "Head"; };
}
fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes
fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep normal") -- Popup the Set Window Fonts dialogue: "keep normal" : vary size & posn, and remember size & posn
-- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup normal") -- Popup the Set Window Fonts dialogue: "popup normal" : vary size & posn, but redisplayed centred
-- fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep") -- Popup the Set Window Fonts dialogue: "keep" : fixed size, vary posn, and only remember posn
-- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup") -- Popup the Set Window Fonts dialogue: "popup": fixed size, vary posn, but redisplayed centred
dialogFont:destroy()
return isFontChosen
end -- function FontDialogue
local function anyMemoControl(anyName,fgColor) -- Compose any control Title and FgColor
local strName = tostring(anyName) -- anyName may be a string, and fgColor is default FgColor
local tipText = nil
if type(anyName) == "table" then -- anyName may be a table = { Title string ; FgColor string ; ToolTip string (optional); }
strName = anyName[1]
fgColor = anyName[2]:match("%d* %d* %d*") or fgColor
tipText = anyName[3]
end
return strName, fgColor, tipText
end -- local function anyMemoControl
local function anyMemoDialogue(strHead,anyHead,strMemo,anyMemo,...) -- Display framed memo dialogue with buttons
local arg = {...} -- Fix for Lua 5.2+
local intButt = 0 -- Returned value if "X Close" button is used
local tblButt = { [0]="X Close"; } -- Button names lookup table
local strHead, fgcHead, tipHead = anyMemoControl(anyHead or "",strHead)
local strMemo, fgcMemo, tipMemo = anyMemoControl(anyMemo or "",strMemo)
-- Create the GUI labels and buttons
local lblMemo = iup.label { Title=strMemo; FgColor=fgcMemo; Tip=tipMemo; TipBalloon=fh.Balloon; Alignment="ACENTER"; Padding=fh.Margin; Expand="YES"; WordWrap="YES"; }
local lblLine = iup.label { Separator="HORIZONTAL"; }
local iupHbox = iup.hbox { Homogeneous="YES"; }
local btnButt = iup.button { }
local strTop = "YES" -- Make dialogue TopMost -- V3.6
local strMode = "popup"
if arg[1] == "Keep Dialogue" then -- Keep dialogue open for a progress message
strMode = "keep dialogue"
lblLine = iup.label { }
if not arg[2] then strTop = "NO" end -- User chooses TopMost -- V3.6
else
if #arg == 0 then arg[1] = "OK" end -- If no buttons listed then default to an "OK" button
for intArg, anyButt in ipairs(arg) do
local strButt, fgcButt, tipButt = anyMemoControl(anyButt,fh.Safe)
tblButt[intArg] = strButt
btnButt = iup.button { Title=strButt; FgColor=fgcButt; Tip=tipButt; TipBalloon=fh.Balloon; Expand="NO"; MinSize="80"; Padding=fh.Margin; action=function() intButt=intArg return iup.CLOSE end; }
iup.Append( iupHbox, btnButt )
end
end
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local iupMemo = iup.dialog { Title=fh.Plugin..fh.Version..strHead; TopMost=strTop; -- TopMost added -- V3.6
iup.vbox { Alignment="ACENTER"; Gap=fh.Gap; Margin=fh.Margin;
iup.frame { Title=strHead; FgColor=fgcHead; Font=fh.FontHead;
iup.vbox { Alignment="ACENTER"; Font=fh.FontBody; lblMemo; lblLine; iupHbox; };
};
};
}
fh.ShowDialogue("Memo",iupMemo,btnButt,strMode) -- Show popup Memo dialogue window with righthand button in focus (if any)
if strMode == "keep dialogue" then return lblMemo, iupMemo end -- Return label & dialogue controls so message can be changed and dialogue destroyed
iupMemo:destroy()
return intButt, tblButt[intButt] -- Return button number & title that was pressed
end -- local function anyMemoDialogue
function fh.MemoDialogue(anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with "Memo" in frame
return anyMemoDialogue(fh.Head,"Memo",fh.Body,anyMemo,...)
end -- function MemoDialogue
function fh.WarnDialogue(anyHead,anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with heading in frame
return anyMemoDialogue(fh.Warn,anyHead,fh.Warn,anyMemo,...)
end -- function WarnDialogue
function fh.GetRegKey(strKey) -- Read Windows Registry Key Value
local luaShell = luacom.CreateObject("WScript.Shell")
local anyValue = nil
if pcall( function() anyValue = luaShell:RegRead(strKey) end ) then
return anyValue -- Return Key Value if found
end
return nil
end -- function GetRegKey
function fh.PutRegKey(strKey,anyValue,strType) -- Write Windows Registry Key Value
local luaShell = luacom.CreateObject("WScript.Shell")
local strAns = nil
if pcall( function() strAns = luaShell:RegWrite(strKey,anyValue,strType) end ) then
return true
end
return nil
end -- function PutRegKey
local function httpRequest(strRequest) -- Luacom http request protected by pcall() below
local http = luacom.CreateObject("winhttp.winhttprequest.5.1")
http:Open("GET",strRequest,false)
http:Send()
return http.Responsebody
end -- local function httpRequest
function fh.VersionInStore(strPlugin) -- Obtain the Version in Plugin Store by Name only -- V3.9
local strVersion = "0"
if strPlugin then
local strFile = fh.MachinePath.."\\VersionInStore "..strPlugin..".dat"
local intTime = os.time() - 2600000 -- Time in seconds a month ago -- V3.9
local tblAttr, strError = lfs.attributes(strFile) -- Obtain file attributes
if not tblAttr or tblAttr.modification < intTime then -- File does not exist or was modified long ago -- V3.9
local lblMemo, iupMemo = fh.MemoDialogue("Checking for updated version in the Family Historian 'Plugin Store'.","Keep Dialogue")
local strErrFile = fh.MachinePath.."\\VersionInStoreInternetError.dat"
local strRequest ="http://www.family-historian.co.uk/lnk/checkpluginversion.php?name="..strPlugin
local isOK, strReturn = pcall(httpRequest,strRequest)
if not isOK then -- Problem with Internet access
local intTime = os.time() - 36000 -- Time in seconds 10 hours ago
local tblAttr, strError = lfs.attributes(strErrFile) -- Obtain file attributes
if not tblAttr or tblAttr.modification < intTime then -- File does not exist or was modified long ago
fhMessageBox(strReturn.."\n The Internet appears to be inaccessible. ","MB_OK","MB_ICONEXCLAMATION")
end
general.SaveStringToFile(strErrFile,strErrFile) -- Update file modified time
else
general.DeleteFile(strErrFile) -- Delete file if Internet is OK
if strReturn ~= nil then
strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits
general.SaveStringToFile(strVersion,strFile) -- Update file modified time and save version -- V4.1
end
end
iupMemo:destroy()
else
strVersion = general.StrLoadFromFile(strFile) -- Retrieve saved latest version -- V4.1
if #strVersion > 9 then general.DeleteFile(strFile) end
end
end
return strVersion or "0"
end -- function VersionInStore
local function intVersion(strVersion) -- Convert version string to comparable integer
local intVersion = 0
local arrNumbers = {}
strVersion:gsub("(%d+)", function(strDigits) table.insert(arrNumbers,strDigits) end) -- V4.1
for i=1,5 do
intVersion = intVersion * 100 + tonumber(arrNumbers[i] or 0)
end
return intVersion
end -- local function intVersion
function fh.CheckVersionInStore(isMemo) -- Check if later Version available in Plugin Store
local strNewVer = fh.VersionInStore(fh.Plugin:gsub(" %- .*",""))
local strOldVer = fh.Version
if intVersion(strNewVer) > intVersion(strOldVer:match("%D*([%d%.]*)")) then
fh.MemoDialogue("Later Version "..strNewVer.." of this Plugin is available from the 'Plugin Store'.")
elseif isMemo then
fh.MemoDialogue("No later Version of this Plugin is available from the 'Plugin Store'.")
end
end -- function CheckVersionInStore
function fh.PluginDataScope(strScope) -- Set default Plugin Data scope to per-Project, or per-User, or per-Machine
strScope = tostring(strScope):lower()
if strScope:match("mach") then -- Per-Machine
strDefaultScope = "Machine"
elseif strScope:match("user") then -- Per-User
strDefaultScope = "User"
end -- Per-Project is default
end -- function PluginDataScope
local function getPluginDataFileName(strScope) -- Get plugin data filename for chosen scope
local isOK, strDataFile = pcall(fhGetPluginDataFileName,strScope)
if not isOK then strDataFile = fhGetPluginDataFileName() end -- Before V5.0.8 parameter is disallowed and default = CURRENT_PROJECT
return strDataFile
end -- local function getPluginDataFileName
local function getDataFiles(strScope) -- Compose the Plugin Data file & path & root names
local strPluginName = fh.Plugin
local strPluginPlain = stringx.plain(strPluginName)
local strDataRoot = "" -- Plugin data file root name -- V4.2
local strDataPath = "" -- Plugin data folder path name
local strDataFile = getPluginDataFileName(strScope) -- Allow plugins with variant filenames to use same plugin data files
strDataFile = strDataFile:gsub("\\"..strPluginPlain:gsub(" ","_"):lower(),"\\"..strPluginName)
strDataFile = strDataFile:gsub("\\"..strPluginPlain..".+%.[D,d][A,a][T,t]$","\\"..strPluginName..".dat")
if #strDataFile > 0 then -- Standalone GEDCOM path is ""
strDataPath = strDataFile:gsub("\\"..strPluginPlain.."%.[D,d][A,a][T,t]$","")
strDataRoot = strDataPath.."\\"..strPluginName
general.MakeFolder(strDataPath) -- V3.4
end
return strDataFile, strDataPath, strDataRoot
end -- local function getDataFiles
function fh.Initialise(strVersion,strPlugin) -- Initialise the GUI module with optional Version & Plugin name
local strAppData = fhGetContextInfo("CI_APP_DATA_FOLDER")
fh.Plugin = fhGetContextInfo("CI_PLUGIN_NAME") -- Plugin Name from file
fh.Version = strVersion or " " -- Plugin Version
if fh.Version == " " then
local strTitle = "\n@Title is missing"
local strAuthor = "\n@Author is missing"
local strVersion = "\n@Version is missing"
local strPlugin = strAppData.."\\Plugins\\"..fh.Plugin..".fh_lua"
if general.FlgFileExists(strPlugin) then
for strLine in io.lines(strPlugin) do -- Read each line from the Plugin file
strPlugin = strLine:match("^@Title:[\t-\r ]*(.*)")
if strPlugin then
strPlugin = strPlugin:gsub("&&","&")
--? if fh.Plugin:match("^"..strPlugin:gsub("(%W)","%%%1")) then
if fh.Plugin:match("^"..stringx.plain(strPlugin)) then
fh.Plugin = strPlugin -- Prefer Title to Filename if it matches
strTitle = nil
else
strTitle = "\n@Title differs from Filename" -- Report abnormality
end
end
if strLine:match("^@Author:%s*(.*)") then -- Check @Author exists
strAuthor = nil
end
fh.Version = strLine:gsub("^@Version:%D*([%d%.]*)%D*"," %1 ")
if fh.Version ~= strLine then -- Obtain the @Version from Plugin file
strVersion = nil
break
end
end
if strTitle or strAuthor or strVersion then -- Report any header abnormalities
fhMessageBox("\nScript Header: "..fh.Plugin..(strTitle or "")..(strAuthor or "")..(strVersion or ""),"MB_OK","MB_ICONEXCLAMATION")
end
else
fhMessageBox("\nPlugin has not been saved!","MB_OK","MB_ICONEXCLAMATION")
end
end
fh.History = fh.Version -- Version History
fh.Plugin = strPlugin or fh.Plugin -- Plugin Name from argument or default from file
fh.DefaultDialogue() -- Default "Font","Main" dialogues
fh.MachineFile,fh.MachinePath,fh.MachineRoot = getDataFiles("LOCAL_MACHINE") -- Plugin data names per machine
fh.PerUserFile,fh.PerUserPath,fh.PerUserRoot = getDataFiles("CURRENT_USER") -- Plugin data names per user
fh.ProjectFile,fh.ProjectPath,fh.ProjectRoot = getDataFiles("CURRENT_PROJECT") -- Plugin data names per project
fh.FhDataPath = fhGetContextInfo("CI_PROJECT_DATA_FOLDER") -- Paths used by Load/SaveFolder for relative folders -- V4.0
fh.PublicPath = fhGetContextInfo("CI_PROJECT_PUBLIC_FOLDER") -- Public data folder path name -- V4.0
if fh.FhDataPath == "" then
fh.FhDataPath = fh.ProjectPath:gsub("\\Plugin Data$","")
end
if fh.PublicPath == "" then
fh.PublicPath = fh.ProjectPath
fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Plugin Data$","%1")
else
general.MakeFolder(fh.PublicPath) -- V3.4
fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Public$","%1")
end
fh.CalicoPie = strAppData:gsub("\\Calico Pie\\.*","\\Calico Pie") -- Program Data Calico Pie path name
fh.ComputerName = os.getenv("COMPUTERNAME") -- Local PC Computer Name
end -- function Initialise
fh.Initialise() -- Initialise module with default values
return fh
end -- local function iup_gui_v3
local iup_gui = iup_gui_v3() -- To access FH IUP GUI build module
-- Global Functions --
--[[
The following keywords are used throughout in variable names and indexes for each website:
AncestryCo for Ancestry Co
FindMyPast for Find My Past
Fam_Search for Family Search
MyHeritage for My Heritage
Canada_Gov for Canada Government Library & Archives
--]]
--- Preset Global Data Definitions --
function PresetGlobalData()
iup_gui.Gap = "2"
iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V4.5
iup_gui.SetUtf8Mode()
IntYearToday = general.GetYearToday() -- V4.2 -- Used in TblIndividualDetails()
end -- function PresetGlobalData
-- Reset Sticky Settings to Default Values --
function ResetDefaultSettings()
iup_gui.CustomDialogue("Main","0x0") -- Custom "Main" dialogue minimum size & centralisation
iup_gui.CustomDialogue("Font","0x0") -- Custom "Font" dialogue minimum size & centralisation
iup_gui.DefaultDialogue("Bars") -- GUI window rastersize and X & Y co-ordinates for "Main","Font","Bars" dialogues
IntCensusList = 1 -- U.K. Census Records
IntAncestryCo = 1 -- Lookup ancestry.co.uk
IntFindMyPast = 1 -- Lookup findmypast.co.uk
IntFam_Search = 1 -- Lookup familysearch.org -- V4.2
IntMyHeritage = 1 -- Lookup myheritage.com -- V4.2
IntCanada_Gov = 1 -- Lookup canada.ca/en -- V5.2
IntRecordList = 1 -- Ancestors of an Individual
StrPartCensus = "OFF" -- Exclude any partial Census Records toggle -- V4.1
StrMissCensus = "OFF" -- Exclude any missing Census not online -- V4.3
StrShortNames = "OFF" -- Fewer than 2 Primary Name components toggle -- V4.1
StrInLocality = "OFF" -- Ignore Facts for at home or abroad toggle
IntMinimumAge = 0 -- Minimum Age to treat as missing from Census
StrEachCensus = "OFF" -- Minimum Age applied to every Census toggle
StrPolygamous = "OFF" -- Polygamous marriage relationships toggle -- V4.3
end -- function ResetDefaultSettings
-- Load Sticky Settings from File --
function LoadSettings()
iup_gui.LoadSettings("Bars") -- Includes "Main","Font" dialogues and "FontSet" & "History"
IntCensusList = tonumber(iup_gui.LoadGlobal("CensusList",IntCensusList))
IntAncestryCo = tonumber(iup_gui.LoadGlobal("AncestryCo",IntAncestryCo))
IntFindMyPast = tonumber(iup_gui.LoadGlobal("FindMyPast",IntFindMyPast))
IntFam_Search = tonumber(iup_gui.LoadGlobal("Fam_Search",IntFam_Search)) -- V4.2
IntMyHeritage = tonumber(iup_gui.LoadGlobal("MyHeritage",IntMyHeritage)) -- V4.2
IntCanada_Gov = tonumber(iup_gui.LoadGlobal("Canada_Gov",IntCanada_Gov)) -- V5.2
IntRecordList = tonumber(iup_gui.LoadGlobal("RecordList",IntRecordList))
StrPartCensus = tostring(iup_gui.LoadGlobal("PartCensus",StrPartCensus)) -- V4.1
StrMissCensus = tostring(iup_gui.LoadGlobal("MissCensus",StrMissCensus)) -- V4.3
StrShortNames = tostring(iup_gui.LoadGlobal("ShortNames",StrShortNames)) -- V4.1
StrInLocality = tostring(iup_gui.LoadGlobal("InLocality",StrInLocality))
IntMinimumAge = tonumber(iup_gui.LoadGlobal("MinimumAge",IntMinimumAge))
StrEachCensus = tostring(iup_gui.LoadGlobal("EachCensus",StrEachCensus))
StrPolygamous = tostring(iup_gui.LoadGlobal("Polygamous",StrPolygamous)) -- V4.3
SaveSettings() -- Save sticky data settings
end -- function LoadSettings
-- Save Sticky Settings to File --
function SaveSettings()
iup_gui.SaveGlobal("CensusList",IntCensusList)
iup_gui.SaveGlobal("AncestryCo",IntAncestryCo)
iup_gui.SaveGlobal("FindMyPast",IntFindMyPast)
iup_gui.SaveGlobal("Fam_Search",IntFam_Search) -- V4.2
iup_gui.SaveGlobal("MyHeritage",IntMyHeritage) -- V4.2
iup_gui.SaveGlobal("Canada_Gov",IntCanada_Gov) -- V5.2
iup_gui.SaveGlobal("RecordList",IntRecordList)
iup_gui.SaveGlobal("PartCensus",StrPartCensus) -- V4.1
iup_gui.SaveGlobal("MissCensus",StrMissCensus) -- V4.3
iup_gui.SaveGlobal("ShortNames",StrShortNames) -- V4.1
iup_gui.SaveGlobal("InLocality",StrInLocality)
iup_gui.SaveGlobal("MinimumAge",IntMinimumAge)
iup_gui.SaveGlobal("EachCensus",StrEachCensus)
iup_gui.SaveGlobal("Polygamous",StrPolygamous) -- V4.3
iup_gui.SaveSettings("Bars") -- Includes "Main","Font","Help" dialogues and "FontSet" & "History"
end -- function SaveSettings
-- Graphical User Interface --
function GUI_MainDialogue()
-- Create GUI controls
local lblCensusList = iup.label { Alignment="ARIGHT"; Title=" Choose a set of Census Records to check :"; }
local lstCensusList = iup.list { DropDown="YES"; Visible_Items="19"; " U. K. Census Records 1790 - 1939"; " U. S. A. Census Records 1790 - 1950"; " Ireland Census Records 1749 - 1921"; " Canada Census Records 1825 - 1945"; " Australia Census Records 1828 - 1921"; " Germany Census Records 1819 - 1939"; " Denmark Census Records 1787 - 1940"; " Norway Census Records 1801 - 1920"; }
local hbxCensusList = iup.hbox { Homogeneous="YES"; lblCensusList; lstCensusList; }
local tglPartCensus = iup.toggle{ Title=" Exclude any partial Census Records that do not comprise nationwide Government collections ? "; } -- V4.1
local hbxPartCensus = iup.hbox { iup.label { Expand="YES"; }; tglPartCensus; }
local tglMissCensus = iup.toggle{ Title=" Exclude any missing Census Records that have no genealogy lookup web site search available ? "; } -- V4.3
local hbxMissCensus = iup.hbox { iup.label { Expand="YES"; }; tglMissCensus; }
local lblAncestryCo = iup.label { Alignment="ARIGHT"; Title=" Choose an AncestryCo lookup web site :"; }
local lstAncestryCo = iup.list { DropDown="YES"; Visible_Items="19"; " Ancestry U. K. : ancestry.co.uk"; " Ancestry U. S. A. : ancestry.com"; " Ancestry Canada : ancestry.ca"; " Ancestry Australia : ancestry.com.au"; " Ancestry Germany : ancestry.de"; " Ancestry Sweden : ancestry.se"; " Ancestry France : ancestry.fr"; " Ancestry Italy : ancestry.it"; " Ancestry lookup is disabled"; }
local hbxAncestryCo = iup.hbox { Homogeneous="YES"; lblAncestryCo; lstAncestryCo; }
local lblFindMyPast = iup.label { Alignment="ARIGHT"; Title=" Choose a FindMyPast lookup web site :"; }
local lstFindMyPast = iup.list { DropDown="YES"; Visible_Items="19"; " FindMyPast U. K. : findmypast.co.uk"; " FindMyPast U. S. A. : findmypast.com"; " FindMyPast Ireland : findmypast.ie"; " FindMyPast Australia : findmypast.com.au"; " FindMyPast lookup is disabled"; }
local hbxFindMyPast = iup.hbox { Homogeneous="YES"; lblFindMyPast; lstFindMyPast; }
local lblFam_Search = iup.label { Alignment="ARIGHT"; Title=" Choose FamilySearch lookup web site :"; }
local lstFam_Search = iup.list { DropDown="YES"; Visible_Items="19"; " FamilySearch.org : familysearch.org"; " FamilySearch lookup is disabled"; } -- V4.2
local hbxFam_Search = iup.hbox { Homogeneous="YES"; lblFam_Search; lstFam_Search; }
local lblMyHeritage = iup.label { Alignment="ARIGHT"; Title=" Choose MyHeritage lookup web site :"; }
local lstMyHeritage = iup.list { DropDown="YES"; Visible_Items="19"; " MyHeritage.com : myheritage.com"; " MyHeritage lookup is disabled"; } -- V4.2
local hbxMyHeritage = iup.hbox { Homogeneous="YES"; lblMyHeritage; lstMyHeritage; }
local lblCanada_Gov = iup.label { Alignment="ARIGHT"; Title=" Choose Canada Gov lookup web site :"; }
local lstCanada_Gov = iup.list { DropDown="YES"; Visible_Items="19"; " Canada.ca English : canada.ca/en"; " Canada.ca Francais : canada.ca/fr"; " Canada Gov lookup is disabled"; } -- V5.2
local hbxCanada_Gov = iup.hbox { Homogeneous="YES"; lblCanada_Gov; lstCanada_Gov; }
local lblRecordList = iup.label { Alignment="ARIGHT"; Title=" Choose the Individuals to investigate :"; }
local lstRecordList = iup.list { DropDown="YES"; Visible_Items="19"; " All the Ancestors of a chosen person"; " All the Descendants of a chosen person"; " All Ancestors & Descendants of a person"; " All the Relations of a chosen person"; " Anyone via the Select Records dialogue"; }
local hbxRecordList = iup.hbox { Homogeneous="YES"; lblRecordList; lstRecordList; }
local tglShortNames = iup.toggle{ Title=" Exclude each person with too few Primary Name parts to create a worthwhile Census search ? "; } -- V4.1
local hbxShortNames = iup.hbox { iup.label { Expand="YES"; }; tglShortNames; }
local tglInLocality = iup.toggle{ Title=" Use their Facts to decide if each person was at home or abroad on the missing Census dates ? "; }
local hbxInLocality = iup.hbox { iup.label { Expand="YES"; }; tglInLocality; }
local lblMinimumAge = iup.label { Alignment="ARIGHT"; Title=" Minimum Age at Census dates : "; }
local txtMinimumAge = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinValue=IntMinimumAge; SpinMax=99; Size="25"; }
local hbxMinimumAge = iup.hbox { Homogeneous="NO"; lblMinimumAge; txtMinimumAge; }
local tglEachCensus = iup.toggle{ Title=" Minimum Age applies to every Census ? "; RightButton="YES"; }
local hbxEachCensus = iup.hbox { iup.label { Expand="YES"; }; tglEachCensus; }
local vbxMinimumAge = iup.vbox { hbxMinimumAge; hbxEachCensus; Gap="6"; Margin="9x0"; }
local btnRunLookup = iup.button{ Title="Lookup Missing Census Facts"; }
local hbxRunLookup = iup.hbox { Homogeneous="YES"; vbxMinimumAge; btnRunLookup; Gap="2"; }
local tglPolygamous = iup.toggle{ Title=" Recognise polygamous relationships ? "; RightButton="YES"; } -- V4.3
local hbxPolygamous = iup.hbox { iup.label { Expand="YES"; }; tglPolygamous; }
local vbxPolygamous = iup.vbox { hbxPolygamous; Gap="6"; Margin="9x4"; }
local btnRedisplay = iup.button{ Title="Redisplay the Last Lookup Page"; } -- V4.3
local hbxRedisplay = iup.hbox { Homogeneous="YES"; vbxPolygamous; btnRedisplay; Gap="2"; }
local btnDefault = iup.button{ Title="Restore Defaults"; }
local btnSetFont = iup.button{ Title="Set Window Fonts"; }
local btnUpdates = iup.button{ Title="Check for Updates"; } -- V5.3
local btnGetHelp = iup.button{ Title=" Help && Advice"; }
local btnDestroy = iup.button{ Title="Close Plugin"; }
local hbxButtons = iup.hbox { Homogeneous="YES"; btnDefault; btnSetFont; btnUpdates; btnGetHelp; btnDestroy; }
-- Combine all the above controls
local allCont = iup.vbox { Gap="4"; Margin="8x8"; hbxCensusList; hbxPartCensus; hbxMissCensus; hbxAncestryCo; hbxFindMyPast; hbxFam_Search; hbxMyHeritage; hbxCanada_Gov; hbxRecordList; hbxShortNames; hbxInLocality; hbxRunLookup; hbxRedisplay; hbxButtons; } -- V4.3
-- Create dialogue
local dialogMain = iup.dialog { Title=iup_gui.Plugin..iup_gui.Version; allCont; }
local function setControls() -- Reset GUI control values
lstCensusList.Value = IntCensusList
lstAncestryCo.Value = IntAncestryCo
lstFindMyPast.Value = IntFindMyPast
lstFam_Search.Value = IntFam_Search -- V4.2
lstMyHeritage.Value = IntMyHeritage -- V4.2
lstCanada_Gov.Value = IntCanada_Gov -- V5.2
lstRecordList.Value = IntRecordList
tglPartCensus.Value = StrPartCensus -- V4.1
tglMissCensus.Value = StrMissCensus -- V4.3
tglShortNames.Value = StrShortNames -- V4.1
tglInLocality.Value = StrInLocality
txtMinimumAge.SpinValue = IntMinimumAge
tglEachCensus.Value = StrEachCensus
tglPolygamous.Value = StrPolygamous -- V4.3
end -- local function setControls
-- Set other GUI control attributes
local strHelpAdvice = "\n Click the 'Help and Advice' below for details" -- V4.1
local tblControls = { { "Font"; "FgColor"; "Padding"; "Tip"; {"TipBalloon";"Balloon"}; {"Expand";"YES"}; {"help_cb";function() iup_gui.HelpDialogue(0) end}; setControls; };
[lblCensusList] = { "FontBody"; "Body"; "10x4"; "Choose country for Census records to be checked"..strHelpAdvice; };
[lstCensusList] = { "FontBody"; "Safe"; "4x4"; "Choose country for Census records to be checked"..strHelpAdvice; };
[tglPartCensus] = { "FontBody"; "Safe"; "4x0"; "Exclude any partial Census record collections?"..strHelpAdvice; };
[tglMissCensus] = { "FontBody"; "Safe"; "4x0"; "Exclude any missing Census records not online?"..strHelpAdvice; }; -- V4.3
[lblAncestryCo] = { "FontBody"; "Body"; "10x4"; "Choose an Ancestry lookup web site to use"..strHelpAdvice; };
[lstAncestryCo] = { "FontBody"; "Safe"; "4x4"; "Choose an Ancestry lookup web site to use"..strHelpAdvice; };
[lblFindMyPast] = { "FontBody"; "Body"; "10x4"; "Choose a FindMyPast lookup web site to use"..strHelpAdvice; };
[lstFindMyPast] = { "FontBody"; "Safe"; "4x4"; "Choose a FindMyPast lookup web site to use"..strHelpAdvice; };
[lblFam_Search] = { "FontBody"; "Body"; "10x4"; "Choose FamilySearch lookup web site to use"..strHelpAdvice; }; -- V4.2
[lstFam_Search] = { "FontBody"; "Safe"; "4x4"; "Choose FamilySearch lookup web site to use"..strHelpAdvice; };
[lblMyHeritage] = { "FontBody"; "Body"; "10x4"; "Choose MyHeritage lookup web site to use"..strHelpAdvice; }; -- V4.2
[lstMyHeritage] = { "FontBody"; "Safe"; "4x4"; "Choose MyHeritage lookup web site to use"..strHelpAdvice; };
[lblCanada_Gov] = { "FontBody"; "Body"; "10x4"; "Choose Canada Gov lookup web site to use"..strHelpAdvice; }; -- V5.2
[lstCanada_Gov] = { "FontBody"; "Safe"; "4x4"; "Choose Canada Gov lookup web site to use"..strHelpAdvice; };
[lblRecordList] = { "FontBody"; "Body"; "10x4"; "Choose the Individuals to include in lookup search"..strHelpAdvice; };
[lstRecordList] = { "FontBody"; "Safe"; "4x4"; "Choose the Individuals to include in lookup search"..strHelpAdvice; };
[tglShortNames] = { "FontBody"; "Safe"; "4x0"; "Exclude each Individual with fewer than 2 Primary Name parts\n delimited by punctuation and not enclosed in [ brackets ] ?"..strHelpAdvice; };
[tglInLocality] = { "FontBody"; "Safe"; "4x0"; "Use all Facts of each Individual to decide if they were\n at home or abroad on missing Census record dates?"..strHelpAdvice; };
[lblMinimumAge] = { "FontBody"; "Body"; "1x0"; "Select minimum Age for certain Census records"..strHelpAdvice; };
[txtMinimumAge] = { "FontHead"; "Safe"; "1x0"; "Select minimum Age for certain Census records"..strHelpAdvice; };
[tglEachCensus] = { "FontBody"; "Safe"; "4x0"; "Apply the minimum Age to ALL Census records?"..strHelpAdvice; };
[tglPolygamous] = { "FontBody"; "Safe"; "4x0"; "Recognise polygamous marriage relationships?"..strHelpAdvice; }; -- V4.3
[btnRunLookup] = { "FontHead"; "Safe"; "1x6"; "Start the missing Census records lookup search"..strHelpAdvice; };
[btnRedisplay] = { "FontHead"; "Safe"; "1x6"; "Redisplay the last Census records lookup search"..strHelpAdvice; }; -- V4.3
[btnDefault ] = { "FontBody"; "Safe"; "1x1"; "Restore all option settings, and window positions and sizes"; };
[btnSetFont ] = { "FontBody"; "Safe"; "1x1"; "Alter the window interface font styles"; };
[btnUpdates] = { "FontBody"; "Safe"; "1x1"; "Check for a later version in the Plugin Store"; }; -- V5.3
[btnGetHelp ] = { "FontBody"; "Safe"; "1x1"; "Access the online Help and Advice pages"; };
[btnDestroy ] = { "FontBody"; "Risk"; "1x1"; "Close this Plugin"; };
}
iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes
local strFolder = iup_gui.MachinePath.."\\" -- Define the folder for the browser files -- V4.3
local strPlugin = iup_gui.Plugin:lower():gsub(" ","") -- Define lowercase and despaced Plugin name
local strRecent = ""
local intAccess = 0 -- Time in seconds long ago
if general.FlgFileExists(strFolder..strPlugin..".css")
and general.FlgFileExists(strFolder..strPlugin..".js") then
for strEntry in lfs.dir(strFolder) do -- Search for files in the folder
if strEntry ~= "." and strEntry ~= ".." then
local strPath = strFolder..strEntry
local tblAttr, strError = lfs.attributes(strPath) -- Obtain file attributes
if not tblAttr then tblAttr = { mode="attrfail"; error=strError; } end
if tblAttr.mode == "file"
and strEntry:match(strPlugin..".+%.html$") then -- Simple file with matching name?
if tblAttr.access > intAccess then
intAccess = tblAttr.access
strRecent = strPath -- Most recent HTML file
end
end
end
end
end
local function setAttributes(iupItem) -- Set the GUI attributes
if lstAncestryCo[IntAncestryCo]:match("disable")
and lstFindMyPast[IntFindMyPast]:match("disable")
and lstFam_Search[IntFam_Search]:match("disable") -- V4.2
and lstMyHeritage[IntMyHeritage]:match("disable") -- V4.2
and lstCanada_Gov[IntCanada_Gov]:match("disable") then -- V5.2
btnRunLookup.Active = "NO" -- Disable Run Lookup button if all websites disabled
else
btnRunLookup.Active = "YES"
end
if general.FlgFileExists(strRecent) then
btnRedisplay.Active = "YES"
else
btnRedisplay.Active = "NO" -- V4.3 -- Disable Redisplay button if no web page HTML file
end
end -- local function setAttributes
function lstCensusList:action(strText,intItem,intState) -- Action for CensusList dropdown
if intState == 1 then
IntCensusList = intItem
SaveSettings()
setAttributes(lstCensusList)
end
end -- function lstCensusList:action
function lstAncestryCo:action(strText,intItem,intState) -- Action for AncestryCo dropdown
if intState == 1 then
IntAncestryCo = intItem
SaveSettings()
setAttributes(lstAncestryCo)
end
end -- function lstAncestryCo:action
function lstFindMyPast:action(strText,intItem,intState) -- Action for FindMyPast dropdown
if intState == 1 then
IntFindMyPast = intItem
SaveSettings()
setAttributes(lstFindMyPast)
end
end -- function lstFindMyPast:action
function lstFam_Search:action(strText,intItem,intState) -- Action for Fam_Search dropdown
if intState == 1 then
IntFam_Search = intItem
SaveSettings()
setAttributes(lstFam_Search)
end
end -- function lstFam_Search:action
function lstMyHeritage:action(strText,intItem,intState) -- Action for MyHeritage dropdown
if intState == 1 then
IntMyHeritage = intItem
SaveSettings()
setAttributes(lstMyHeritage)
end
end -- function lstMyHeritage:action
function lstCanada_Gov:action(strText,intItem,intState) -- Action for Canada_Gov dropdown -- V5.2
if intState == 1 then
IntCanada_Gov = intItem
SaveSettings()
setAttributes(lstCanada_Gov)
end
end -- function lstCanada_Gov:action
function lstRecordList:action(strText,intItem,intState) -- Action for RecordList dropdown
if intState == 1 then
IntRecordList = intItem
SaveSettings()
setAttributes(lstRecordList)
end
end -- function lstRecordList:action
function tglPartCensus:action(intState) -- Action for exclude partial Census toggle -- V4.1
StrPartCensus = tglPartCensus.Value
SaveSettings()
tglPartCensus.Tip = tglPartCensus.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglPartCensus:action
function tglMissCensus:action(intState) -- Action for exclude missing Census toggle -- V4.3
StrMissCensus = tglMissCensus.Value
SaveSettings()
tglMissCensus.Tip = tglMissCensus.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglMissCensus:action
function tglShortNames:action(intState) -- Action for fewer than 2 Name parts toggle -- V4.1
StrShortNames = tglShortNames.Value
SaveSettings()
tglShortNames.Tip = tglShortNames.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglShortNames:action
function tglInLocality:action(intState) -- Action for In Locality toggle
StrInLocality = tglInLocality.Value
SaveSettings()
tglInLocality.Tip = tglInLocality.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglInLocality:action
function txtMinimumAge:spin_cb(intSpinValue) -- Action for Minimum Age spin value
IntMinimumAge = intSpinValue
end -- function txtMinimumAge:spin_cb
function tglEachCensus:action(intState) -- Action for Each Census toggle
StrEachCensus = tglEachCensus.Value
SaveSettings()
tglEachCensus.Tip = tglEachCensus.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglEachCensus:action
function tglPolygamous:action(intState) -- Action for Polygamous toggle -- V4.3
StrPolygamous = tglPolygamous.Value
SaveSettings()
tglPolygamous.Tip = tglPolygamous.Tip -- Refresh tooltip otherwise it vanishes in XP!
end -- function tglPolygamous:action
function btnRunLookup:action() -- Action for Run Lookup button
dialogMain.Active = "NO"
btnRunLookup.Active = "NO" -- V4.1 -- Disable GUI while running lookup
btnRedisplay.Active = "NO"
local strCensusList = lstCensusList[lstCensusList.Value]
local dicLookupSite = { -- V4.3 -- Lookup web sites where element names are used in Template & Census profiles
AncestryCo = lstAncestryCo[lstAncestryCo.Value]:match(" : -([^ ]-ancestry.-)$") or "" ;
FindMyPast = lstFindMyPast[lstFindMyPast.Value]:match(" : -([^ ]-findmypast.-)$") or "" ;
Fam_Search = lstFam_Search[lstFam_Search.Value]:match(" : -([^ ]-familysearch.-)$") or "" ; -- V4.2
MyHeritage = lstMyHeritage[lstMyHeritage.Value]:match(" : -([^ ]-myheritage.-)$") or "" ; -- V4.2
Canada_Gov = lstCanada_Gov[lstCanada_Gov.Value]:match(" : -([^ ]-canada.-)$") or "" ; -- V5.2
}
local strRecordList = lstRecordList[lstRecordList.Value]:match(" (Ancestors.- of )")
or lstRecordList[lstRecordList.Value]:match(" (Descendant.- of )")
or lstRecordList[lstRecordList.Value]:match(" (Relations.- of )") -- Extract Individuals to check from GUI option
local dicUserOption = { -- V4.3 User optional mode settings
PartCensus = StrPartCensus == "ON" ; -- V4.1
MissCensus = StrMissCensus == "ON" ; -- V4.3
ShortNames = StrShortNames == "ON" ; -- V4.1
InLocality = StrInLocality == "ON" ;
MinimumAge = IntMinimumAge ;
EachCensus = StrEachCensus == "ON" ;
Polygamous = StrPolygamous == "ON" ; -- V4.3
}
RunLookup(strCensusList,dicLookupSite,strRecordList,dicUserOption)
return iup.CLOSE
end -- function btnRunLookup:action
function btnRedisplay:action() -- Action for Redisplay button -- V4.3
dialogMain.Active = "NO"
btnRedisplay.Active = "NO"
local isOK, intErrorCode, strErrorText = fhShellExecute(strRecent)
return iup.CLOSE
end -- function btnRedisplay:action
function btnDefault:action() -- Action for Restore Defaults button
ResetDefaultSettings()
setControls() -- Reset controls & redisplay Main dialogue
iup_gui.ShowDialogue("Main")
iup_gui.DefaultDialogue() -- V5.3
SaveSettings() -- Save sticky data settings
setAttributes(btnDefault)
end -- function btnDefault:action
function btnSetFont:action() -- Action for Set Window Font button
btnSetFont.Active = "NO"
iup_gui.FontDialogue(tblControls)
SaveSettings() -- Save sticky data settings
btnSetFont.Active = "YES"
end -- function btnSetFont:action
function btnSetFont:button_cb(intButton,intPress) -- Action for mouse right-click on Set Window Fonts button
if intButton == iup.BUTTON3 and intPress == 0 then
iup_gui.BalloonToggle() -- Toggle tooltips Balloon mode
end
end -- function btnSetFont:button_cb
local function doExecute(strExecutable, strParameter) -- Invoke FH Shell Execute API -- V4.5
local function ReportError(strMessage)
iup_gui.WarnDialogue( "Shell Execute Error",
"ERROR: "..strMessage.." :\n"..strExecutable.."\n"..strParameter.."\n\n",
"OK" )
end -- local function ReportError
return general.DoExecute(strExecutable, strParameter, ReportError)
end -- local function doExecute
function btnUpdates:action() -- Action for Check for Updates button -- V5.3
local strFile = iup_gui.MachinePath.."\\VersionInStore Lookup Missing Census Facts.dat"
general.DeleteFile(strFile)
iup_gui.CheckVersionInStore(true) -- Notify if later Version
end -- function btnUpdates:action
local strHelp = "https://pluginstore.family-historian.co.uk/page/help/lookup-missing-census-facts"
function btnGetHelp:action() -- Action for Help & Advice button -- V4.5
doExecute( strHelp )
fhSleep(3000,500)
dialogMain.BringFront="YES"
end -- function btnGetHelp:action
function btnDestroy:action() -- Action for Close button
return iup.CLOSE
end -- function btnDestroy:action
setAttributes(btnDefault)
iup_gui.ShowDialogue("Main",dialogMain,btnDestroy) -- Display Main GUI Dialogue and optionally Version History Help
end -- function GUI_MainDialogue
-- Java Script --
StrWebPageJS =
[[
/*
Table sorting script by Joost de Valk, check it out at http://www.joostdevalk.nl/code/sortable-table/.
Based on a script from http://www.kryogenix.org/code/browser/sorttable/.
Distributed under the MIT license: http://www.kryogenix.org/code/browser/licence.html .
Copyright (c) 1997-2007 Stuart Langridge, Joost de Valk.
Version 1.5.7
*/
/* You can change these values */
var image_path = "http://www.fhug.org.uk/programs/js/sortable/";
var image_up = "arrow-up.gif";
var image_down = "arrow-down.gif";
var image_none = "arrow-none.gif";
var europeandate = true;
var alternate_row_colors = false;
/* MBT
For debugging use console.log("x:",x);
In Firefox use Tools > Web Developer > Web Console Ctrl+Shft+K or Browsr Console Ctrl+Shft+J
*/
/* Don't change anything below this unless you know what you're doing */
addEvent(window, "load", sortables_init);
var SORT_COLUMN_INDEX;
var SORT_DIRECTION;
var thead = false;
function sortables_init() {
// Find all tables with class sortable and make them sortable
if (!document.getElementsByTagName) return;
tbls = document.getElementsByTagName("table");
for (ti=0;ti 0) {
if (t.tHead && t.tHead.rows.length > 0) {
var firstRow = t.tHead.rows[t.tHead.rows.length-1];
thead = true;
} else {
var firstRow = t.rows[0];
}
}
if (!firstRow) return;
// We have a first row: assume it's the header, and make its contents clickable links
for (var i=0;i'+txt+'
';
}
}
if (alternate_row_colors) {
alternate(t);
}
}
function ts_getInnerText(el) {
if (typeof el == "string") return el;
if (typeof el == "undefined") { return el };
if (el.innerText) return el.innerText; //Not needed but it is faster
var str = "";
var cs = el.childNodes;
var l = cs.length;
for (var i = 0; i < l; i++) {
switch (cs[i].nodeType) {
case 1: //ELEMENT_NODE
str += ts_getInnerText(cs[i]);
break;
case 3: //TEXT_NODE
str += cs[i].nodeValue;
break;
}
}
return str;
}
function ts_resortTable(lnk, clid) {
var span;
for (var ci=0;ci ';
newRows.reverse();
span.setAttribute('sortdir','up');
} else {
ARROW = '
';
span.setAttribute('sortdir','down');
}
// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
// don't do sortbottom rows
for (i=0; i ';
}
}
}
span.innerHTML = ARROW;
alternate(t);
}
function getParent(el, pTagName) {
if (el == null) {
return null;
} else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) {
return el;
} else {
return getParent(el.parentNode, pTagName);
}
}
function sort_date(date) {
// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
if (date == "") {
if (SORT_DIRECTION == 'down') { // MBT force blank cells to bottom depending on sort 'down' or 'up'
return "00000000";
} else {
return "99999999";
}
}
var dt = "00000000";
var regexp = /^(.*[\s\-]\d\d\d\d)/; // MBT
var result = regexp.exec(date); // MBT remove trailing text after 4 digit year
date = result[1]; // MBT
if (date.length == 11) {
var mtstr = date.substr(3,3);
mtstr = mtstr.toLowerCase();
switch(mtstr) {
case "jan": var mt = "01"; break;
case "feb": var mt = "02"; break;
case "mar": var mt = "03"; break;
case "apr": var mt = "04"; break;
case "may": var mt = "05"; break;
case "jun": var mt = "06"; break;
case "jul": var mt = "07"; break;
case "aug": var mt = "08"; break;
case "sep": var mt = "09"; break;
case "oct": var mt = "10"; break;
case "nov": var mt = "11"; break;
case "dec": var mt = "12"; break;
default: var mt = "00"; // MBT removed comment to enable default and cope with "bef" & "aft" & "___"
}
dt = date.substr(7,4)+mt+date.substr(0,2);
return dt;
} else if (date.length == 10) {
if (europeandate == false) {
dt = date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
return dt;
} else {
dt = date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
return dt;
}
} else if (date.length == 8) {
var yr = date.substr(6,2);
if (parseInt(yr) < 50) {
yr = '20'+yr;
} else {
yr = '19'+yr;
}
if (europeandate == true) {
dt = yr+date.substr(3,2)+date.substr(0,2);
return dt;
} else {
dt = yr+date.substr(0,2)+date.substr(3,2);
return dt;
}
} else if (date.length == 5) {
dt = date.substr(1,4)+"0000"; // MBT " yyyy"
return dt;
}
return dt;
}
function ts_sort_date(a,b) {
var dt1 = sort_date(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
var dt2 = sort_date(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
if (dt1==dt2) {
return 0;
}
if (dt1
{TitleName}
{TitleName}
The following people appear to have no Census information when they could be alive.
People with no life dates recorded are assessed on their estimated life dates.
Women who are married at the time of a Census are shown with their married name if available.
Where there is no marriage date the Family Date is taken from the 1st child's birth date if any.
The Plugin Help & Advice for Frequently Asked Questions has tips on refining lookup search filters for each website.
No. {AncestryCo}{FindMyPast}{Fam_Search}{MyHeritage}{Canada_Gov}
Individual's Name
Birth Year
Life Dates
Spouse Family at Census
Family Date
Father's Name
Mother's Name
]]
StrWebTailTemplate = -- Grid inserted between StrWebPageTemplate and StrWebTailTemplate -- V4.3
[[
]]
StrWebHeadTemplate = -- Replaces website headings in StrWebPageTemplate above
[[
{WebAddress} ]]
StrWebLineTemplate = -- Replaces {WebGrid} in StrWebPageTemplate above
[[
{ItemNumber} {AncestryCo}{FindMyPast}{Fam_Search}{MyHeritage}{Canada_Gov}
{Individual}
{Birth_Year}
{Life_Dates}
{FamilyName}
{FamilyDate}
{FatherName}
{MotherName}
]]
StrMissingTemplate = -- Replaces website cells in StrWebLineTemplate above when no hyperlink
[[
{CensusLink}{LocaleLink} ]] -- V4.3 " n/a" not now needed as display is gray
-- {CensusYear} n/a ]]
StrWebSiteTemplate = -- Replaces website cells in StrWebLineTemplate above with a hyperlink -- V4.2
[[
{CensusLink}{LocaleLink} ]]
-- These templates are derived by peforming manual online searches and copying the required URL components.
TblTemplate = { -- Replaces {WebSiteCol} in StrWebSiteTemplate above with a URL -- V4.2
AncestryCo = { -- AncestryCo templates updated -- V4.7
-- e.g. https://www.ancestry.co.uk/search/categories/1911uki/?name=Esther_ANDERSON -- or /collections/7884/
-- Birth &birth=1840_london-england&birth_x=5-0-0
-- Gender &gender=f
-- Spouse &spouse=Iain_ANDERSON
-- Father &father=Laurie_CAMERON
-- Mother &mother=Allison_WATSON
Lookup = [[https://www.{WebAddress}/search/{RecordCode}/?name={GivenNames}_{TheSurname}{Birth_Data}{Gender_Sex}{Relatives}]] ;
Birth = [[&birth={Birth_Year}_{BirthPlace}&birth_x={Birth_Span}-0-0]] ; -- Date Exact ±10 often gives more results than Broad!
Gender = [[&gender={Gender_Sex}]] ;
Spouse = [[&spouse={GivenNames}_{TheSurname}]] ;
Mother = [[&mother={GivenNames}_{TheSurname}]] ;
Father = [[&father={GivenNames}_{TheSurname}]] ;
};
FindMyPast = { -- FindMyPast tempates updated -- V4.7
-- e.g. https://www.findmypast.co.uk/search/results?datasetname=1911+census+for+england+%26+wales&sid=103&firstname=esther&firstname_variants=true&lastname=anderson&lastname_variants=true
-- Birth &yearofbirth=1911&yearofbirth_offset=15
-- Gender &gender=female
-- Spouse &spousefirstname=iain&spousefirstname_variants=true&spouselastname=anderson&spouselastname_variants=true
-- Other &firstnamesother=iain&firstnamesother_variants=true&lastnamesother=anderson&lastnamesother_variants=true
Lookup = [[https://www.{WebAddress}/search/results?datasetname={RecordCode}&firstname={GivenNames}&firstname_variants=true&lastname={TheSurname}&lastname_variants=true{Birth_Data}{Gender_Sex}{Relatives}]] ;
Birth = [[&yearofbirth={Birth_Year}&yearofbirth_offset={Birth_Span}]] ;
Gender = [[&gender={Gender_Sex}]] ;
Spouse = [[&spousefirstname={GivenNames}&spousefirstname_variants=true&spouselastname={TheSurname}&spouselastname_variants=true]] ;
Other = [[&firstnamesother={GivenNames}&firstnamesother_variants=true&lastnamesother={TheSurname}&lastnamesother_variants=true]] ;
};
Fam_Search = { -- FamilySearch templates updated -- V2.6
-- e.g. https://www.familysearch.org/search/record/results?count=20&offset=0&f.recordType=3&f.collectionId=1921547&q.givenName=Esther&q.surname=ANDERSON
-- Birth &q.birthLikeDate.from=1900&q.birthLikeDate.to=1924&q.birthLikePlace=
-- Gender &c.sex=on&f.sex=female
-- Spouse &q.spouseGivenName=Iain&q.spouseSurname=ANDERSON
-- Mother &q.motherGivenName=Allison&q.motherSurname=WATSON
-- Father &q.fatherGivenName=Laurie&q.fatherSurname=CAMERON
Lookup = [[https://www.{WebAddress}/search/record/results?count=20&offset=0&f.recordType=3{RecordCode}&q.givenName={GivenNames}&q.surname={TheSurname}{Birth_Data}{Gender_Sex}{Relatives}]] ;
Birth = [[&q.birthLikeDate.from={Birth_Year}&q.birthLikeDate.to={Birth_Span}&q.birthLikePlace={BirthPlace}]] ; -- {Birth_Year} is earliest year; {Birth_Span} is latest year
Gender = [[&c.sex=on&f.sex={Gender_Sex}]] ;
Spouse = [[&q.spouseGivenName={GivenNames}&q.spouseSurname={TheSurname}]] ;
Mother = [[&q.motherGivenName={GivenNames}&q.motherSurname={TheSurname}]] ; -- or {MaidenName} ?
Father = [[&q.fatherGivenName={GivenNames}&q.fatherSurname={TheSurname}]] ;
};
MyHeritage = { -- MyHeritage templates updated -- V4.7 StrBuildLookup(...) replaces space and dot in Names by '%2F3' Low Ring
-- e.g. https://www.myheritage.com/research/collection-10446/1911-england-wales-census?s=1&formId=&formMode=1&useTranslation=1&exactSearch=&action=query&view_mode=tabular&p=1&qname=Name+fn.Esther+ln.ANDERSON
-- Gender +g.F
-- Birth &qevents-event1=Event+et.birth+ey.1911&qevents-any/1event_1=Event+et.any+ep.+epmo.similar&qevents=List
-- Spouse &qrelative_relativeName=Name+fn.Iain+ln.ANDERSON&qrelatives-relative=Relative+rt.spouse+rn.*qrelative_relativeName
-- Mother &qaddRelative_1_addRelativeName=Name+fn.Allison+ln.WATSON&qrelatives-addRelative_1=Relative+rt.mother+rn.*qaddRelative_1_addRelativeName
-- Father &qaddRelative_2_addRelativeName=Name+fn.Laurie+ln.CAMERON&qrelatives-addRelative_2=Relative+rt.father+rn.*qaddRelative_2_addRelativeName&qrelatives=List
Lookup = [[https://www.{WebAddress}/research/collection-{RecordCode}?s=1&formId=&formMode=1&useTranslation=1&exactSearch=&action=query&view_mode=tabular&p=1&qname=Name+fn.{GivenNames}+ln.{TheSurname}{Gender_Sex}{Birth_Data}{Relatives}&qrelatives=List]] ;
Birth = [[&qevents-event1=Event+et.birth+ey.{Birth_Year}&qevents-any/1event_1=Event+et.any+ep.{BirthPlace}+epmo.similar&qevents=List]] ; -- +me.true+mer.{Birth_Span} but only 1, 2, 5, 10, 20 allowed, so better to allow flexible match
Gender = [[+g.{Gender_Sex}]] ;
Spouse = [[&qrelative_relativeName=Name+fn.{GivenNames}+ln.{TheSurname}&qrelatives-relative=Relative+rt.spouse+rn.*qrelative_relativeName]] ;
Mother = [[&qaddRelative_1_addRelativeName=Name+fn.{GivenNames}+ln.{TheSurname}&qrelatives-addRelative_1=Relative+rt.mother+rn.*qaddRelative_1_addRelativeName]] ;
Father = [[&qaddRelative_2_addRelativeName=Name+fn.{GivenNames}+ln.{TheSurname}&qrelatives-addRelative_2=Relative+rt.father+rn.*qaddRelative_2_addRelativeName]] ;
};
Canada_Gov = { -- Canada Gov templates updated -- V5.2
-- e.g. https://recherche-collection-search.bac-lac.gc.ca/eng/Home/Result?DataSource=Genealogy|Census&ApplicationCode=29&FirstName=Esther&LastName=ANDERSON&YearOfBirth=1900-1924&PlaceOfBirth=Canada&DataSourceSel=Genealogy|Census&SEARCH_TYPE=SEARCH_CENSUS_ADVANCED&
-- Gender &GenderCode=2
-- Birth YearOfBirth=1900-1924&PlaceOfBirth=Canada
-- https://recherche-collection-search.bac-lac.gc.ca/fra/accueil/resultat?DataSource=Genealogy%7CCensus&ApplicationCode=121&DataSourceSel=Genealogy%7CCensus&SEARCH_TYPE=SEARCH_CENSUS_ADVANCED
Lookup = [[https://recherche-collection-search.bac-lac.gc.ca/{WebAddress}?DataSource=Genealogy%7CCensus&ApplicationCode={RecordCode}&FirstName={GivenNames}&LastName={TheSurname}{Gender_Sex}{Birth_Data}{Relatives}&DataSourceSel=Genealogy%7CCensus&SEARCH_TYPE=SEARCH_CENSUS_ADVANCED&]] ;
Birth = [[&YearOfBirth={Birth_Year}-{Birth_Span}&PlaceOfBirth={BirthPlace}]] ;
Gender = [[&GenderCode={Gender_Sex}]] ;
};
}
-- End Web Page Templates --
-- Build Census Profile Table for chosen Region and Records --
function TblCensusProfile(strCensusList,dicLookupSite,dicUserOption)
-- strCensusList Chosen set of Census entries
-- dicLookupSite Lookup web site details -- V4.3
-- dicUserOption User optional mode settings -- V4.3
-- PartCensus Partial Census Records mode -- V4.1
-- MissCensus Missing Census Records mode -- V4.3
-- Polygamous Polygamy recognised mode -- V4.3
--[[
Year Replaces {CensusYear} in Templates, and used to check if Census Event exists
When Replaces {CensusLink} in Template hyperlinks and converted to Date below
Date FH Simple Date Object determines if Individual was alive and in Locality at Census
Part Boolean for partial records that are not nationwide Goverment collections -- V4.1
Child Boolean for children included in Census used with Minimum Age setting
Birth Boolean for birth year included in Census records and Templates
Gender Boolean for gender sex included in Census records and Templates
Spouse Boolean for "spouse****name=" allowed in FindMyPast Template
Other Boolean for "****namesother=" allowed in FindMyPast Template
Locale Names of places to determine if Individual was in Locality at Census
.Record Replaces {RecordCode} in Web Site Template to choose Census Year records -- V4.2
.Locale Replaces {LocaleLink} in Web Site Template hyperlinks for subset records -- V4.2
( Nil entry in .Record & .Locale columns indicate no website records & no locale )
--]]
local strEngland_Wales = "England,Wales,Isle of Man,Jersey,Guernsey,Channel Islands" -- V4.3
local strUnitedKingdom = strEngland_Wales .. ",Scotland,UK,U.K." -- V4.3
local strUSA_1890_CivilWarVeterans = "1890+u.s.+census%2c+civil+war+union+veterans+and+widows"
local tblProfile = { }
local tblColumn = { "Year"; "When"; "Part"; "Child"; "Birth"; "Gender"; "Spouse"; "Other"; "Locale"; "AncestryCo" ; "FindMyPast" ; "Fam_Search" ; "MyHeritage" ; "Canada_Gov" ; }
local tblLookup = {
["U. K."] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1790; " 1790"; true ; true ; true ; false ; false ; false; "Corfe Castle,Corfe,Dorset" ; { " " ; "Corfe Castle" }; { "corfe+castle+and+district+1790+census" ; "Corfe Castle" }; { " " ; "Corfe Castle" }; { " " ; "Corfe Castle" }; { " " ; "Corfe Castle" }; };
{ 1801; " 1801"; true ; true ; false; false ; false ; false; "Dartford,Kent" ; { " " ; "Dartford, Kent" }; { "1801+kent%2c+dartford+census" ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; };
{ 1821; " 1821"; true ; false; false; false ; false ; false; "Dartford,Kent" ; { " " ; "Dartford, Kent" }; { "1821+kent%2c+dartford+census" ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; { " " ; "Dartford, Kent" }; };
{ 1831; "30 May 1831"; true ; true ; false; false ; false ; false; "Nether Hallam,Hallam,Sheffield" ; { " " ; "Nether Hallam" }; { "nether+hallam%2c+sheffield+1831+census" ; "Nether Hallam" }; { " " ; "Nether Hallam" }; { " " ; "Nether Hallam" }; { " " ; "Nether Hallam" }; };
{ 1841; " 6 Jun 1841"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1841uki" }; { "1841+england%2c+wales+%26+scotland+census" }; { "1493745 2016000" }; { "10150/1841-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1851; "30 Mar 1851"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1851uki" }; { "1851+england%2c+wales+%26+scotland+census" }; { "2563939 2028673" }; { "10151/1851-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1861; " 7 Apr 1861"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1861uki" }; { "1861+england%2c+wales+%26+scotland+census" }; { "1493747 2028677" }; { "10152/1861-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1871; " 2 Apr 1871"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1871uki" }; { "1871+england%2c+wales+%26+scotland+census" }; { "1538354 2028678" }; { "10153/1871-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1881; " 3 Apr 1881"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1881uki" ; "(free index)" }; { "1881+england%2c+wales+%26+scotland+census" ; "(free index)" }; { "2562194 2046756" }; { "10154/1881-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1891; " 5 Apr 1891"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1891uki" }; { "1891+england%2c+wales+%26+scotland+census" }; { "1865747 2046943" }; { "10155/1891-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1901; "31 Mar 1901"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { "categories/1901uki" }; { "1901+england%2c+wales+%26+scotland+census" }; { "1888129" ; "England&Wales" }; { "10156/1901-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1911; " 2 Apr 1911"; false; true ; true ; true ; false ; true ; strEngland_Wales ; { "categories/1911uki" ; "England&Wales" }; { "1911+census+for+england+%26+wales" ; "England&Wales" }; { "1921547" ; "England&Wales" }; { "10446/1911-england-wales-census" ; "England&Wales" }; { " " ; "England&Wales" }; };
{ 1911; " 2 Apr 1911"; false; true ; true ; true ; false ; true ; "Scotland" ; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; }; -- 1911 Scotland Census not available -- V4.3
{ 1921; "19 Jun 1921"; false; true ; true ; true ; false ; true ; strEngland_Wales ; { "categories/1921uki" ; "England&Wales" }; { "1921+census+of+england+%26+wales" ; "England&Wales" }; { " " ; "England&Wales" }; { " " ; "England&Wales" }; { " " ; "England&Wales" }; }; -- 1921 UK Census is partly available -- V4.7 -- V5.1
{ 1921; "19 Jun 1921"; false; true ; true ; true ; false ; true ; "Scotland" ; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; { " " ; "Scotland" }; }; -- 1921 Scotland Census not available -- V4.7
{ 1939; "29 Sep 1939"; false; true ; true ; true ; false ; true ; strEngland_Wales ; { "collections/61596" ; "Register E&W" }; { "1939+register" ; "Register E&W" }; { " " ; "Register E&W" }; { "10678/1939-register-of-england-wales" ; "Register E&W" }; { " " ; "Register E&W" }; }; -- 1939 Register England & Wales -- V3.9-- V4.3 -- V5.1
-- { 1951; " 8 Apr 1951"; false; true ; true ; true ; false ; true ; strUnitedKingdom ; { }; { }; { }; { }; { }; }; -- 1951 UK Census not yet available -- V4.3
};
["U. S. A."] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1790; " 2 Aug 1790"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/5058" }; { "us+census+1790" }; { "1803959" }; { "10120/1790-united-states-federal-census" }; { }; };
{ 1800; " 4 Aug 1800"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/7590" }; { "us+census+1800" }; { "1804228" }; { "10121/1800-united-states-federal-census" }; { }; }; -- V4.2 added 'ancestry'
{ 1810; " 6 Aug 1810"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/7613" }; { "us+census+1810" }; { "1803765" }; { "10122/1810-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1820; " 7 Aug 1820"; true ; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/7734" }; { "us+census+1820" }; { "1803955" }; { "10123/1820-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1830; " 1 Jun 1830"; false; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/8058" }; { "us+census+1830" }; { "1803958" }; { "10125/1830-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1840; " 1 Jun 1840"; false; false; false; false ; false ; false; "America,USA,U.S.A." ; { "collections/8057" }; { "us+census+1840" }; { "1786457" }; { "10124/1840-united-states-federal-census" }; { }; }; -- V4.2 "
{ 1850; " 1 Jun 1850"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/8054" }; { "us+census+1850" }; { "1401638 1420440 1420441" }; { "10126/1850-united-states-federal-census" }; { }; }; -- V4.2 " Gender = true
{ 1860; " 1 Jun 1860"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7667" }; { "us+census+1860" }; { "1473181" }; { "10127/1860-united-states-federal-census" }; { }; }; -- V4.2 " Gender = true
{ 1870; " 1 Jun 1870"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7163" }; { "us+census+1870" }; { "1438024" }; { "10128/1870-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1880; " 1 Jun 1880"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/6742" ; "(free)" }; { "us+census+1880" }; { "1417683" }; { "10129/1880-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1890; " 1890"; true ; false; false; true ; false ; false; "America,USA,U.S.A." ; { "collections/8667" ; "Veterans" }; { strUSA_1890_CivilWarVeterans ; "Veterans" }; { }; { }; { }; };
{ 1890; " 2 Jun 1890"; true ; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { "collections/5445" }; { "us+census+1890" }; { "1610551" }; { "10130/1890-united-states-federal-census" }; { }; }; -- V4.2 Child & Birth & Gender = true
{ 1900; " 1 Jun 1900"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7602" }; { "us+census+1900" }; { "1325221" }; { "10131/1900-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1910; "15 Apr 1910"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/7884" }; { "us+census+1910" }; { "1727033" }; { "10132/1910-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1920; " 1 Jan 1920"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/6061" }; { "us+census+1920" }; { "1488411" }; { "10133/1920-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1930; " 1 Apr 1930"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/6224" }; { "us+census+1930" }; { "1810731 1821205" }; { "10134/1930-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1940; " 1 Apr 1940"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/2442" ; "(free)" }; { "us+census+1940" }; { "2000219" }; { "10053/1940-united-states-federal-census" }; { }; }; -- V4.2 Gender = true
{ 1950; " 1 Apr 1950"; false; true ; true ; true ; false ; true ; "America,USA,U.S.A." ; { "collections/62308" ; "(free)" }; { }; { }; { "11006/1950-united-states-federal-census" ; "(free)" }; { }; }; -- V4.7 Gender = true
--[==[ for testing
{ 1957; " 1 Apr 1957"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1958; " 1 Apr 1958"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1960; " 1 Apr 1960"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1970; " 1 Apr 1970"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1980; " 1 Apr 1980"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 1990; " 1 Apr 1990"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 2000; " 1 Apr 2000"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 2005; " 1 Apr 2005"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
{ 2010; " 1 Apr 2010"; false; true ; true ; true ; false ; false; "America,USA,U.S.A." ; { }; { }; { }; { }; { }; };
--]==]
};
["Ireland"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1749; " 1749"; true ; false; false; false ; false ; false; "Ireland,Eire,Elphin" ; { }; { "the+census+of+elphin+1749" ; "Elphin" }; { }; { }; { }; };
{ 1766; " 1766"; true ; false; false; false ; false ; false; "Ireland,Eire" ; { "collections/5990" ; "Religious" }; { }; { }; { }; { }; };
{ 1821; "28 May 1821"; true ; true ; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/62025" }; { "ireland+census+1821-1851" }; { "2345228" }; { }; { }; };
{ 1831; "30 Mar 1831"; true ; false; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/62025" }; { "ireland+census+1821-1851" }; { "2334949" }; { }; { }; };
{ 1841; " 6 Jun 1841"; true ; false; true ; true ; false ; false; "Ireland,Eire" ; { "collections/70792" }; { "ireland+census+search+forms+1841+%26+1851" }; { "2346276 2346275" }; { }; { }; };
{ 1851; "30 Mar 1851"; true ; false; true ; true ; false ; false; "Ireland,Eire" ; { "collections/70792" }; { "ireland+census+search+forms+1841+%26+1851" }; { "2340880 2346275" }; { }; { }; };
{ 1851; "30 Mar 1851"; true ; false; true ; false ; false ; false; "Ireland,Eire,Cork,Dublin" ; { "collections/48535" ; "Cork" }; { "the+1851+dublin+city+census" ; "Dublin" }; { }; { }; { }; };
{ 1901; "31 Mar 1901"; false; true ; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/70667" }; { "ireland+census+1901" }; { "1626180" }; { "10199/1901-ireland-census" }; { }; }; -- V4.2 Gender = true
{ 1911; " 2 Apr 1911"; false; true ; true ; true ; false ; true ; "Ireland,Eire" ; { "collections/70564" }; { "ireland+census+1911" }; { "2854327" }; { "10198/1911-ireland-census" }; { }; }; -- V4.2 Gender = true
{ 1921; "19 Jun 1921"; false; true ; true ; true ; false ; true ; "Ireland,Eire" ; { }; { }; { }; { }; { }; }; -- 1921 Census not yet available -- V4.3
};
["Canada"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1825; " 1825"; true ; false; false; false ; false ; false; "Canada" ; { "collections/9807" }; { "lower+canada+census+1825" }; { "1834346" }; { "30265/1825-canada-lower-canada-census" }; { "121" }; };
{ 1831; " 1831"; true ; false; false; false ; false ; false; "Canada" ; { }; { }; { "1834329" }; { }; { "122" }; };
{ 1842; " 1842"; true ; false; false; false ; false ; false; "Canada" ; { "collections/9808" }; { "lower+canada+census+1842" }; { "1834340 1834342" }; { "30264/1842-canada-lower-canada-census" }; { "123~124" }; };
{ 1851; "12 Jan 1852"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/1061" }; { "canada+census+1851" ; "(free)" }; { "1325192" }; { "10522/1851-canada-census" }; { "26" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1861; "14 Jan 1861"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/1570" }; { "canada+census+1861" ; "(free)" }; { "1325208 1460163 1460164 1460172 1460173" }; { "10521/1861-canada-census" }; { "120" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1870; "16 Jun 1870"; true ; true ; true ; true ; false ; true ; "Canada,Manitoba" ; { }; { }; { }; { }; { "125" }; }; -- V5.2
{ 1871; " 2 Apr 1871"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/1578" }; { "canada+census+1871" ; "(free)" }; { "1551612 1554429" }; { "10520/1871-canada-census" }; { "2~29" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1881; " 4 Apr 1881"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/1577" }; { "canada+census+1881" ; "(free)" }; { "1804541" }; { "10441/1881-canada-census" }; { "16" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1891; " 6 Apr 1891"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/1274" }; { "canada+census+1891" ; "(free)" }; { "1583536" }; { "10440/1891-canada-census" }; { "27" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1901; "31 Mar 1901"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/8826" }; { "canada+census+1901" ; "(free)" }; { "1584557" }; { "10448/1901-canada-census" }; { "28" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1906; "24 Jun 1906"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/8827" }; { }; { "1584925" }; { "10457/1906-canada-census-of-alberta-saskatchewan-manitoba"}; { " 3" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1911; " 1 Jun 1911"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/8947" }; { "canada+census+1911" ; "(free)" }; { "2143998" }; { "10447/1911-canada-census" }; { " 4" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1916; " 1 Jun 1916"; true ; true ; true ; true ; false ; true ; "Canada" ; { "collections/1556" }; { }; { "1529118" }; { "10458/1916-canada-census-of-alberta-saskatchewan-manitoba"}; { "30" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1921; " 1 Jun 1921"; false; true ; true ; true ; false ; true ; "Canada" ; { "collections/8991" }; { }; { }; { "10690/1921-canada-census" }; { "137" }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1921; " 1921"; true ; true ; true ; true ; false ; false; "Canada,Newfoundland" ; { "collections/61491" ; "Newfoundland" }; { }; { "2226517" ; "Newfoundland" }; { }; { }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1926; " 1 Jun 1926"; true ; true ; true ; true ; false ; true ; "Canada" ; { }; { }; { }; { }; { "146" }; }; -- V5.2
{ 1931; " 1 Jun 1931"; false; true ; true ; true ; false ; true ; "Canada" ; { }; { }; { }; { }; { "1008" }; }; -- V5.2
{ 1935; " 1935"; true ; true ; true ; true ; false ; false; "Canada,Newfoundland" ; { "collections/61493" ; "Newfoundland" }; { }; { "2246711" ; "Newfoundland" }; { }; { }; }; -- V4.2 Gender = true -- V4.7 Birth = true
{ 1945; " 1945"; true ; true ; true ; true ; false ; false; "Canada,Newfoundland" ; { "collections/61492" ; "Newfoundland" }; { }; { "2246699" ; "Newfoundland" }; { }; { }; }; -- V4.2 Gender = true -- V4.7 Birth = true
};
["Australia"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1828; " 1 Nov 1828"; false; true ; true ; false ; false ; false; "Australia,NSW" ; { "collections/1186" ; "NSW" }; { "new+south+wales%2c+1828+census+householders'+returns" ; "NSW" }; { "2177300" ; "NSW" }; { }; { }; };
{ 1841; " 2 Mar 1841"; false; true ; false; false ; false ; false; "Australia,NSW" ; { "collections/1214" ; "NSW" }; { "new+south+wales+1841+census" ; "NSW" }; { "2317857" ; "NSW" }; { }; { }; };
{ 1841; " 1841"; false; true ; true ; false ; true ; false; "Australia,SA" ; { }; { "south+australia+census" ; "SA" }; { }; { }; { }; };
{ 1881; " 2 Mar 1881"; false; true ; true ; false ; false ; false; "Australia,NT" ; { }; { "northern-territory-census" ; "NT" }; { }; { }; { }; };
{ 1891; " 5 Apr 1891"; false; true ; false; false ; false ; false; "Australia,NSW" ; { "collections/1733" ;"NSW (free index)" }; { "new+south+wales+1891+census" ; "NSW" }; { "2317858" ; "NSW" }; { }; { }; };
{ 1901; "31 Mar 1901"; false; true ; false; false ; false ; false; "Australia,NSW" ; { "collections/1738" ;"NSW (free index)" }; { "new+south+wales+1901+census" ; "NSW" }; { }; { }; { }; };
{ 1911; " 2 Apr 1911"; false; true ; true ; false ; false ; false; "Australia,NT" ; { }; { "northern-territory-census" ; "NT" }; { }; { }; { }; };
{ 1921; " 2 Apr 1921"; false; true ; true ; false ; false ; false; "Australia,NT" ; { }; { "northern-territory-census" ; "NT" }; { }; { }; { }; };
};
["Germany"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1819; " Aug 1819"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1867; " 3 Dec 1867"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1890; " 1 Dec 1890"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1900; " 1 Dec 1900"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1905; " 1 Dec 1905"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1910; " 1 Dec 1910"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1916; " 5 Dec 1916"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1917; " 5 Dec 1917"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1919; " 8 Oct 1919"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1925; "16 Jun 1925"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1933; "16 Jun 1933"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
{ 1939; "17 May 1939"; false; true ; true ; true ; false ; false; "Germany,Deutschland" ; { }; { }; { }; { }; { }; }; -- Not easily searchable online -- V4.9
};
["Denmark"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1787; " 1 Jul 1787"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61724" ; "(free)" }; { }; { }; { "10685/1787-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1801; " 1 Feb 1801"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61726" ; "(free)" }; { }; { }; { "10684/1801-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1834; " 1 Feb 1834"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61725" ; "(free)" }; { }; { "2771431" }; { "10681/1834-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1840; " 1 Feb 1840"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61727" ; "(free)" }; { }; { "2778651" }; { "10680/1840-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1845; " 1 Feb 1845"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61735" ; "(free)" }; { }; { "2778652" }; { "10673/1845-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1850; " 1 Feb 1850"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61734" ; "(free)" }; { }; { "2778653" }; { "10193/1850-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1855; " 1 Feb 1855"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61737" ; "(free)" }; { }; { "2778654" }; { "10258/1855-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1860; " 1 Feb 1860"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61736" ; "(free)" }; { }; { "2778655" }; { "10194/1860-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1870; " 1 Feb 1870"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61733" }; { }; { "2778656" }; { "10195/1870-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1880; " 1 Feb 1880"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61729" ; "(free)" }; { }; { "2778657" }; { "10189/1880-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1890; " 1 Feb 1890"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61732" ; "(free)" }; { }; { "2800928" }; { "10188/1890-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1901; " 1 Feb 1901"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { "collections/61731" ; "(free)" }; { }; { "2800929" }; { "10187/1901-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1906; " 1 Feb 1906"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2800931" }; { "10253/1906-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1911; " 1 Feb 1911"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2691890" }; { "10184/1911-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1916; " 1 Feb 1916"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2717471" }; { "10196/1916-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1921; " 1 Feb 1921"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2721601" }; { "10197/1921-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1925; " 5 Nov 1925"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2718007" }; { "10190/1925-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1930; " 5 Nov 1930"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { "2800932" }; { "10181/1930-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1940; " 5 Nov 1940"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { }; { "10706/1940-denmark-census" }; { }; }; -- V4.7 -- V4.9
{ 1950; " 5 Nov 1950"; false; true ; true ; true ; false ; false; "Denmark,Danmark" ; { }; { }; { }; { }; { }; }; -- V4.7 -- V4.9
};
["Norway"] = {
-- Year; When ; Part ; Child; Birth; Gender; Spouse; Other; Locale ; { AncestryCo.Record ; AncestryCo.Locale }; { FindMyPast.Record ; FindMyPast.Locale }; { Fam_Search.Record ; Fam_Search.Locale }; { MyHeritage.Record ; MyHeritage.Locale }; { Canada_Gov.Record ; Canada_Gov.Locale }; };
{ 1801; " 1 Feb 1801"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/61749" }; { }; { "3733603" }; { "10814/1801-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1865; "31 Dec 1865"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/61753" }; { }; { "3756102" }; { "10807/1865-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1870; "31 Dec 1870"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/62015" }; { }; { "4135961" }; { "10937/1870-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1875; "31 Dec 1875"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/61751" }; { }; { "1529106" }; { "10936/1875-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1891; " 1 Jan 1891"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/60603" }; { }; { "4067726" }; { "10730/1891-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1900; " 3 Dec 1900"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/60604" }; { }; { "3744863" }; { "10731/1900-norway-census" }; { }; }; -- V4.7 -- V4.9 or 2 Dec 1900
{ 1910; " 1 Dec 1910"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { "collections/60605" }; { }; { }; { "10732/1910-norway-census" }; { }; }; -- V4.7 -- V4.9
{ 1920; " 1 Dec 1920"; false; true ; true ; true ; false ; false; "Norway,Norge" ; { }; { }; { }; { }; { }; }; -- V4.7 -- V4.9
};
}
for strRegion, tblRegion in pairs (tblLookup) do -- Find matching country entry for profile
if strCensusList:match(strRegion) then
for intRow, tblRow in ipairs (tblRegion) do -- Assign each row of data
local intRow = #tblProfile + 1 -- V4.1
tblProfile[intRow] = { Polygamous=dicUserOption.Polygamous }-- V4.3
local dicProfile = tblProfile[intRow]
local isNoRecord = true -- V4.3 -- Assume no online search
for intCol, anyCol in ipairs (tblRow) do -- Assign each column value
local strCol = tblColumn[intCol]
if strCol:match("^%u%l+$") -- V4.3 -- Unconditional column name
or #dicLookupSite[strCol] > 1 then -- V4.3 -- Web site conditional column
if type(anyCol) == "table" then -- V4.2 -- Adjust website Record & Locale tables
local strRecord = anyCol[1] or " "
local strLocale = anyCol[2] or " "
if #strRecord > 2 then isNoRecord = false end -- V4.3 -- Check for online search
if #strLocale > 1 then
strLocale = "
"..strLocale -- Locale goes below Census Date using
strLocale = strLocale:gsub(" "," ") -- V4.3 -- Force onto one line with no-break spaces
end
if strCol == "Fam_Search" then -- V4.6
strRecord = strRecord:gsub("(%d+) ?","&f.collectionId=%1")
end
anyCol = { Record = strRecord; Locale = strLocale; }
end
dicProfile[strCol] = anyCol
end
end
local datCensus = fhNewDate()
datCensus:SetValueAsText(dicProfile.When) -- Convert each When string to FH Simple Date Object
dicProfile.Date = datCensus:Clone() -- Save cloned FH Simple Date Object
dicProfile.When = dicProfile.When:gsub(" "," ") -- Force no-break spaces into When to allow sorting
if ( dicUserOption.PartCensus and dicProfile.Part ) -- V4.1 -- Remove partial Census records
or ( dicUserOption.MissCensus and isNoRecord ) then -- V4.3 -- Remove any with no online search
table.remove(tblProfile)
end
end
break
end
end
return tblProfile
end -- function TblCensusProfile
function StrFilteredNameForURI(strName) -- V2.2
strName = strName:gsub('%[%[.-%]%]','') -- Exclude double bracketed text
strName = strName:gsub("'.-'",""):gsub('".-"',"") -- Exclude quoted text
strName = strName:gsub('[%?%*%^"~%%]','') -- Remove '?' & '*' wildcards used by Ancestry & FamilySearch, '^' that upsets FMP, and '"' & '~' & '%' that upset FamilySearch
strName = encoder.StrEncode_URI(strName) -- Encode for URI
strName = strName:gsub('[%+%.]',' ') -- Keep space instead of '+' and '.' in URI for MyHeritage conversion to "%2F3"
return strName
end -- function StrFilteredNameForURI
-- Is Date Available -- -- V4.1
function DateExists(ptrDate)
if ptrDate:IsNull()
or fhGetValueAsDate(ptrDate):IsNull() then return false end -- Date Phrase with no Date
return true
end -- function DateExists
-- Format Family Date for Java Script Sort --
function StrFamilyDate(ptrFamDate)
-- ptrFamDate Marriage or 1st Child Birth Date
local strFamDate = fhGetItemText(ptrFamDate,"~:ABBREV2") -- Formats: "dd mmm yyyy?", "aft yyyy", "bef yyyy", "yyyy-yyyy", "____-yyyy", "yyyy-____"
if strFamDate ~= "" then
strFamDate = strFamDate:gsub("^c%. (.-%d%d%d%d)","%1?")
local intFamDate = string.len(strFamDate:gsub("(%d%d%d%d)[%d%?%-_]*","%1")) -- Ignoring any ? or - or _ after 4 digit year
strFamDate = string.rep(" ",11 - intFamDate)..strFamDate -- 17 May 2014 Pad date with leading spaces to 11 chars long up to 4 digit year
strFamDate = encoder.StrEncode_XML(strFamDate):gsub(" "," ")
end
return strFamDate
end -- function StrFamilyDate
-- Current Family At Census Date Timeline --
function CurrentFamily(ptrIndi,tblProfile,tblDetails) -- 15 Nov 2015 V3.9 completely rewritten
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- tblDetails Details of Individual
if not tblDetails.Family then
tblDetails.Family = {} -- Create Family As Spouse timeline
local ptrFamsTag = fhNewItemPtr()
local ptrFamIndi = fhNewItemPtr()
local ptrFamDate = fhNewItemPtr()
ptrFamsTag:MoveTo(ptrIndi,"~.FAMS") -- First Family As Spouse of Individual
while ptrFamsTag:IsNotNull() do
ptrFamIndi = fhGetValueAsLink(ptrFamsTag)
ptrFamDate:MoveTo(ptrFamIndi,"~.MARR.DATE")
if not DateExists(ptrFamDate) then -- If no Marriage Date use Birth of First Child -- v4.1
ptrFamDate:MoveTo(ptrFamIndi,"~.CHIL>BIRT.DATE")
end
if DateExists(ptrFamDate) then -- V4.1
local strStatus = fhGetItemText(ptrFamIndi,"~._STAT")
if strStatus ~= "Never Married"
and strStatus ~= "Unmarried Couple" then -- Family couple married or with child, and Date defined
for intTag, strTag in ipairs ({ "~.HUSB[1]>"; "~.WIFE[1]>"; "~.HUSB[2]>"; "~.WIFE[2]>"; }) do
local ptrSpouse = fhGetItemPtr(ptrFamIndi,strTag)
if not ptrSpouse:IsSame(ptrIndi) then -- Spouse found, so find surname of a wife's husband
local strHusband = ""
if fhGetItemText(ptrIndi,"~.SEX") == "Female" then
strHusband = fhGetItemText(ptrFamIndi,"~.HUSB>NAME:SURNAME")
end
local intFamily = #tblDetails.Family
if intFamily > 0 then
local tblFamily = tblDetails.Family[intFamily]
if not tblFamily.FamLink
and fhGetValueAsDate(tblFamily.FamDate):Compare(fhGetValueAsDate(ptrFamDate)) > 0 then
table.remove(tblDetails.Family) -- Remove date with previous Spouse that is after current family date
end
end
-- Save current Family as Spouse, Event Date, and Husband Surname for partners together
table.insert(tblDetails.Family,{ FamLink=ptrFamIndi:Clone(); FamDate=ptrFamDate:Clone(); Husband=strHusband; })
local ptrTags = fhNewItemPtr()
local ptrFams = fhNewItemPtr()
local ptrDate = fhNewItemPtr()
ptrTags:MoveTo(ptrSpouse,"~.FAMS") -- First Family As Spouse of Spouse
while ptrTags:IsNotNull() do
ptrFams = fhGetValueAsLink(ptrTags)
if not ptrFams:IsSame(ptrFamIndi) then
ptrDate:MoveTo(ptrFams,"~.MARR.DATE")
if not DateExists(ptrDate) then -- If no Marriage Date use Birth of First Child -- V4.1
ptrDate:MoveTo(ptrFams,"~.CHIL>BIRT.DATE")
end
if DateExists(ptrDate) -- Save date when Spouse with another partner, only after date with original partner -- V4.1
and fhGetValueAsDate(ptrDate):Compare(fhGetValueAsDate(ptrFamDate)) > 0 then
table.insert(tblDetails.Family,{ FamDate=ptrDate:Clone(); })
end
end
ptrTags:MoveNext("SAME_TAG")
end
for intTag, strTag in ipairs ({ "~.DEAT.DATE"; "~.BURI.DATE"; "~.CREM.DATE"; }) do
ptrDate:MoveTo(ptrSpouse,strTag)
if DateExists(ptrDate) then -- Save date of Demise of current partner -- V4.3
if tblProfile.Polygamous and -- Polygamous relationships recognised? -- V4.3
not tblDetails.Family[#tblDetails.Family].FamLink then
table.remove(tblDetails.Family) -- Remove date of Marriage/First Child with another partner -- V4.3
end
table.insert(tblDetails.Family,{ FamDate=ptrDate:Clone(); })
break
end
end
break
end
end
end
end
for intTag, strTag in ipairs ({ "~.DIV.DATE"; "~.DIVF.DATE"; "~.ANUL.DATE"; "~.EVEN-SEPARATION.DATE"; }) do
ptrFamDate:MoveTo(ptrFamIndi,strTag)
if DateExists(ptrFamDate) then -- Save date of Divorce, Divorce Filed, Annulment, or Separation -- V4.1
table.insert(tblDetails.Family,{ FamDate=ptrFamDate:Clone(); })
break
end
end
ptrFamsTag:MoveNext("SAME_TAG")
end
-- Reverse sort the Family As Spouse timeline by Date, so Event Date before Census Date is easy to find below
table.sort(tblDetails.Family,function(fam1,fam2) return fhGetValueAsDate(fam1.FamDate):Compare(fhGetValueAsDate(fam2.FamDate)) > 0 end)
end
local datCensusDate = tblProfile.Date
if tblDetails.CenDate:IsNull() -- V4.2
or tblDetails.CenDate:Compare(datCensusDate) ~= 0 then -- Update details for different Census Date -- V4.2
local ptrFamLink = fhNewItemPtr()
if tblDetails.CenDate:Compare(datCensusDate) == 1 then -- V5.0 -- Details later than census date need resetting
tblDetails.Sname = tblDetails.Sborn
tblDetails.sname = tblDetails.sborn -- V4.2 -- Restore birth Surname = female maiden name
tblDetails.SNAME = tblDetails.SBORN
end
tblDetails.CenDate = datCensusDate -- Save Census Date
tblDetails.FamLink = ptrFamLink -- Clear Family As Spouse and Event Date
tblDetails.FamDate = ""
for intFamily, tblFamily in ipairs (tblDetails.Family) do -- Search Family As Spouse timeline until Event Date before Census Date
if fhGetValueAsDate(tblFamily.FamDate):Compare(datCensusDate) <= 0 then
ptrFamLink = tblFamily.FamLink
if ptrFamLink then -- Family As Spouse link, so set Current Family details
local strHusband = tblFamily.Husband
if strHusband ~= "" then -- Married women use husband's surname
tblDetails.SNAME = strHusband -- V4.9
tblDetails.Sname = StrFilteredNameForURI(strHusband)
tblDetails.sname = encoder.StrEncode_XML(strHusband)
end
tblDetails.FamLink = ptrFamLink:Clone() -- Family As Spouse link and Date (Marriage or 1st Born)
tblDetails.FamDate = StrFamilyDate(tblFamily.FamDate)
end
break -- Event Date without Family link leaves Family details blank (Divorce, Annulment, partner Marries another, etc)
end
end
end
end -- function CurrentFamily
-- Get Individual Name & Birth & Death Details --
TblDetails = {} -- Save details in table cache for quick subsequent lookup
function TblIndividualDetails(ptrIndi,tblProfile,intRecId)
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- intRecId Individual Record Id
intRecId = intRecId or fhGetRecordId(ptrIndi)
local tblDetails = TblDetails[intRecId]
if not tblDetails then
TblDetails[intRecId] = {}
tblDetails = TblDetails[intRecId]
local strGiven = fhGetItemText(ptrIndi,"~.NAME:GIVEN_ALL")
local strSname = fhGetItemText(ptrIndi,"~.NAME:SURNAME")
local strPlace = fhGetItemText(ptrIndi,"~.BIRT.PLAC")
tblDetails.RecId = intRecId
tblDetails.Dates = fhCallBuiltInFunction("LifeDates",ptrIndi):gsub("c%. (.-%d%d%d%d)","%1?"):gsub(" "," ")
tblDetails.GIVEN = strGiven -- V4.9
tblDetails.Given = StrFilteredNameForURI(strGiven) -- V4.2
tblDetails.given = encoder.StrEncode_XML(strGiven)
tblDetails.SBORN = strSname -- V4.9
tblDetails.Sborn = StrFilteredNameForURI(strSname) -- V4.2
tblDetails.sborn = encoder.StrEncode_XML(strSname) -- V4.2
tblDetails.SNAME = strSname -- V4.9
tblDetails.Sname = tblDetails.Sborn -- V4.2
tblDetails.sname = tblDetails.sborn -- V4.2
tblDetails.Place = StrFilteredNameForURI(strPlace) -- V4.2
tblDetails.Death = general.EstimatedDeathDates(ptrIndi,9).Max -- V4.3 -- Fix erroneous EstimatedDeathDate function
local dicDate = general.EstimatedBirthDates(ptrIndi,9) -- V4.1 -- Fix erroneous EstimatedBirthDate function
local yearMin = dicDate.Min:GetYear()
local yearMax = math.min(dicDate.Max:GetYear(),IntYearToday) -- Max year is limited to current year -- V4.2
local intYear = dicDate.Mid:GetYear() -- V4.2
local intSpan = 0
local strYear = "" -- Birth date Year & Span for each web site -- V4.2
local strSpan = "" -- More efficient here once, than every time needed in a lookup
if intYear > 0 then -- Estimated Birth year available
intSpan = math.ceil ( ( yearMax - yearMin ) / 2 ) -- Tolerance span = rounded up ( max year - min year ) / 2
if intSpan < 40 then -- Round up even exact Dates to ±5
if intSpan <= 5 then
yearMin = intYear - 5 -- Extend Fam_Search range
yearMax = intYear + 5
end
intSpan = math.ceil( math.max(intSpan,5) / 5) * 5 -- Round up low Span to ±5, ±10, ±20, ±40
if intSpan == 15 then intSpan = 20 -- Round up 15 to 20
elseif intSpan > 20 and intSpan < 40 then intSpan = 40 end -- Round up 21-39 to 40
end
strYear = tostring(intYear or "") -- Fact date Year & Span for each web site -- V4.2
strSpan = tostring(intSpan or "") -- More efficient here once, than every time needed in a lookup
end
tblDetails.Birth = intYear
tblDetails.Range = strSpan
tblDetails.Year = {}
tblDetails.Span = {}
tblDetails.Year.AncestryCo = strYear
tblDetails.Span.AncestryCo = tostring(math.min(intSpan,10)) -- Exact ±10 may give greater or fewer results than Broad!
tblDetails.Year.FindMyPast = strYear
tblDetails.Span.FindMyPast = strSpan
tblDetails.Year.Fam_Search = tostring(yearMin)
tblDetails.Span.Fam_Search = tostring(yearMax)
tblDetails.Year.MyHeritage = strYear
tblDetails.Span.MyHeritage = strSpan
tblDetails.Year.Canada_Gov = tostring(yearMin) -- V5.2
tblDetails.Span.Canada_Gov = tostring(yearMax)
local strSex = fhGetItemText(ptrIndi,"~.SEX") -- V4.2 -- Sex format for each web site
tblDetails.Sex = {}
tblDetails.Sex.AncestryCo = strSex:lower():match("^([fm])") -- V4.7
tblDetails.Sex.FindMyPast = strSex
tblDetails.Sex.Fam_Search = strSex:lower() -- V4.6
tblDetails.Sex.MyHeritage = strSex:match("^([FM])")
local dicSex = {Male=1;Female=2;Unknown=8;}
tblDetails.Sex.Canada_Gov = ({Male=1;Female=2;Unknown=8;})[strSex] -- V5.2
tblDetails.CenDate = fhNewDate() -- V4.2
end
if tblProfile then -- Use mid age based on mid year rather than max age -- V4.2
tblDetails.Age = tblProfile.Year - tblDetails.Birth
CurrentFamily(ptrIndi,tblProfile,tblDetails) -- Family at Census details
else
tblDetails.Age = 0
tblDetails.SNAME = tblDetails.SBORN
tblDetails.Sname = tblDetails.Sborn -- Restore birth Surname = female maiden name for Father & Mother column -- V4.2
tblDetails.sname = tblDetails.sborn
tblDetails.CenDate = fhNewDate() -- 15 Nov 2015 V3.9 -- V4.2
end
return TblDetails[intRecId]
end -- function TblIndividualDetails
-- Check Death Date if Relation alive and Name exists -- -- 15 Nov 2015 V3.9 -- V4.2
function IfRelationExists(tblRelation,tblProfile)
-- tblRelation tblDetails of related Individual
-- tblProfile Census Profile of Country
local isName = ( #tblRelation.Given > 0 or #tblRelation.Sname > 0 ) -- Does their Name exist -- V4.2
local datDeathDate = fhNewDate()
datDeathDate:SetSimpleDate(tblRelation.Death)
return ( datDeathDate:Compare(tblProfile.Date) >= 0 and isName ) -- Is alive at Census Date and Name exists?
end -- function IfRelationExists
-- Get the Spouse if married or the Parents if unmarried and young at time of Census --
function StrRelatives(ptrIndi,tblProfile,ptrFamily,intAge,strSpouseTemplate,strMotherTemplate,strFatherTemplate)
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- ptrFamily Link to Family
-- intAge Age of Individual
-- str***Template Relative Web Template
local strRelatives = ""
local strRelation = ""
local tblRelation = {}
local ptrRelation = fhNewItemPtr()
if strSpouseTemplate and ptrFamily:IsNotNull() then -- Get Spouse at time of Census
if fhGetItemText(ptrIndi,"~.SEX") == "Female" then
ptrRelation:MoveTo(ptrFamily,"~.HUSB>")
else
ptrRelation:MoveTo(ptrFamily,"~.WIFE>")
end
if ptrRelation:IsNotNull() then
tblRelation = TblIndividualDetails(ptrRelation,tblProfile)
if IfRelationExists(tblRelation,tblProfile) then -- Spouse is alive at current Census Date with a Name -- 15 Nov 2015 V3.9 -- V4.2
strRelation = strSpouseTemplate
strRelation = strRelation:replace("{GivenNames}",tblRelation.Given)
strRelation = strRelation:replace("{TheSurname}",tblRelation.Sname) -- 13 Apr 2014 V3.2 use wife's last husband's surname
strRelatives = strRelatives..strRelation
end
end
end
if not intAge -- Unconditionally include Parents (FamilySearch and MyHeritage) -- V4.2
or ( strRelatives == "" and intAge <= 25 ) then -- Only include Parents of unmarried young Individuals
-- Does NOT cope with adoptive/foster Parents !!!!
ptrRelation:MoveTo(ptrIndi,"~.~MOTH>") -- Get Mother
if strMotherTemplate and ptrRelation:IsNotNull() then
tblRelation = TblIndividualDetails(ptrRelation,tblProfile)
if IfRelationExists(tblRelation,tblProfile) then -- Mother is alive at current Census Date with a Name -- 15 Nov 2015 V3.9 -- V4.2
strRelation = strMotherTemplate
strRelation = strRelation:replace("{GivenNames}",tblRelation.Given)
strRelation = strRelation:replace("{TheSurname}",tblRelation.Sname)
strRelation = strRelation:replace("{MaidenName}",tblRelation.Sborn) -- V4.2
strRelatives = strRelatives..strRelation
end
end
ptrRelation:MoveTo(ptrIndi,"~.~FATH>") -- Get Father
if strFatherTemplate and ptrRelation:IsNotNull() then
tblRelation = TblIndividualDetails(ptrRelation)
if IfRelationExists(tblRelation,tblProfile) then -- Father is alive at current Census Date with a Name -- 15 Nov 2015 V3.9 -- V4.2
strRelation = strFatherTemplate
strRelation = strRelation:replace("{GivenNames}",tblRelation.Given)
strRelation = strRelation:replace("{TheSurname}",tblRelation.Sname)
strRelatives = strRelatives..strRelation
end
end
end
return strRelatives
end -- function StrRelatives
-- Build Web Site Lookup -- V4.2
function StrBuildLookup(strWebAddress,tblProfile,strWebSite,ptrIndi,tblDetails)
-- strWebAddress Web Site Domain Name
-- tblProfile Census Profile of Country
-- strWebSite Index to Census and Details data
-- ptrIndi Individual of Interest
-- tblDetails Details of Individual
local strTemplate = ""
if #strWebAddress > 1 then -- Include chosen website
if not ptrIndi then -- Build table heading
strTemplate = StrWebHeadTemplate:replace("{WebAddress}",strWebAddress)
elseif #tblProfile[strWebSite].Record < 2 then -- V4.3 -- Build missing Census year entry
strTemplate = StrMissingTemplate
strTemplate = strTemplate:replace("{CensusLink}",tblProfile.When)
strTemplate = strTemplate:replace("{LocaleLink}",tblProfile[strWebSite].Locale)
else -- Build table data lookup anchor tag
strWebAddress = ({ en="eng/home/result"; fr="fra/accueil/resultat"; xx=strWebAddress; })[strWebAddress:match("canada.ca/(.-)$") or "xx"] -- V5.2
strTemplate = TblTemplate[strWebSite].Lookup
strTemplate = strTemplate:replace("{WebAddress}",strWebAddress)
strTemplate = strTemplate:replace("{RecordCode}",(tblProfile[strWebSite].Record:gsub("^ ",""))) -- V5.2
strTemplate = strTemplate:replace("{GivenNames}",tblDetails.Given) -- V4.1 Use all Given names instead of tblDetails.First
strTemplate = strTemplate:replace("{TheSurname}",tblDetails.Sname)
local strBirth = "" -- Birth Year details
if tblProfile.Birth and tblDetails.Birth > 0 then -- Birth date in Census and Event -- V4.2
strBirth = TblTemplate[strWebSite].Birth -- Set the Year and Span values for web site template
strBirth = strBirth:replace("{Birth_Year}",tblDetails.Year[strWebSite])
strBirth = strBirth:replace("{Birth_Span}",tblDetails.Span[strWebSite])
end
strTemplate = strTemplate:replace("{Birth_Data}",strBirth)
strTemplate = strTemplate:replace("{BirthPlace}",tblDetails.Place)
local strGender = "" -- Gender Sex details
if tblProfile.Gender then -- Gender data in Census -- V4.2
local strSex = tblDetails.Sex[strWebSite]
if strSex then -- Gender Sex value for web site template
strGender = TblTemplate[strWebSite].Gender
strGender = strGender:replace("{Gender_Sex}",strSex)
end
end
strTemplate = strTemplate:replace("{Gender_Sex}",strGender)
local intAge = nil -- Spouse, Father, Mother details
if strWebSite == "AncestryCo"
or strWebSite == "FindMyPast" then
intAge = tblDetails.Age -- Mid age for AncestryCo and FindMyPast
end
local strSpouse = TblTemplate[strWebSite].Spouse -- Relatives templates
local strMother = TblTemplate[strWebSite].Mother
local strFather = TblTemplate[strWebSite].Father
if strWebSite == "FindMyPast" then -- Special rules for FindMyPast
strSpouse = nil
strMother = nil
strFather = nil
if tblProfile.Other then
local strOther = TblTemplate[strWebSite].Other
strSpouse = strOther
strMother = strOther
strFather = strOther
end
if tblProfile.Spouse then
strSpouse = TblTemplate[strWebSite].Spouse
end
end
local strRelatives = StrRelatives(ptrIndi,tblProfile,tblDetails.FamLink,intAge,strSpouse,strMother,strFather)
strTemplate = strTemplate:replace("{Relatives}",strRelatives)
if strWebSite == "MyHeritage" then -- Needs "%2F3" space substitute only within Names
strTemplate = strTemplate:replace(" ","%2F3") -- U+02F3 Modifier Letter Low Ring space sub in Names
end
strTemplate = StrWebSiteTemplate:replace("{WebSiteCol}",strTemplate)
strTemplate = strTemplate:replace("{CensusLink}",tblProfile.When)
strTemplate = strTemplate:replace("{LocaleLink}",tblProfile[strWebSite].Locale)
end
end
return strTemplate
end -- function StrBuildLookup
-- Iterate List of Records to Search -- -- V4.3 -- Avoids slow "IsAncestor/Descendant/RelativeOf" functions
function TblRecordList(tblRecordList,strRecordList)
-- tblRecordList Individual Records List
-- strRecordList Relatives Record selection mode
if strRecordList then -- Ancestors or Descendants or Relations needed
local arrRelative = { } -- Array of Individual Record Id & Pointers
local dicRelative = { } -- Dictionary of processed Individual Record Id
local dicFamilies = { } -- Dictionary of processed Family Record Id
local function newRelative(ptrRec) -- Add new Relative to list
if ptrRec:IsNotNull() then
local intRec = fhGetRecordId(ptrRec)
if not dicRelative[intRec] then
dicRelative[intRec] = true
table.insert(arrRelative,{ Id=intRec; Rec=ptrRec:Clone(); })
if #arrRelative % 1000 == 0 then fhSleep(10,7) end -- Prevent FH Stopped Working/Not Responding message
return true
end
end
return false
end -- local function newRelative
local function getAncestor(ptrRec) -- Get next Ancestor generation
if newRelative(ptrRec) then
local ptrFamc = fhGetItemPtr(ptrRec,"~.FAMC") -- Cater for multiple parent families
while ptrFamc:IsNotNull() do
local ptrFam = fhGetValueAsLink(ptrFamc)
getAncestor(fhGetItemPtr(ptrFam,"~.HUSB[1]>")) -- Cater for both sex & same sex parents
getAncestor(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
getAncestor(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
getAncestor(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
ptrFamc:MoveNext("SAME_TAG")
end
end
end -- local function getAncestor
local function getDescends(ptrRec) -- Get next Descendant generation
if newRelative(ptrRec) then
local ptrFams = fhGetItemPtr(ptrRec,"~.FAMS") -- Cater for multiple spouse families
while ptrFams:IsNotNull() do
local ptrFam = fhGetValueAsLink(ptrFams)
getDescends(fhGetItemPtr(ptrFam,"~.HUSB[1]>")) -- Cater for both sex & same sex partners
getDescends(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
getDescends(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
getDescends(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
local ptrChil = fhGetItemPtr(ptrFam,"~.CHIL") -- Get all children
while ptrChil:IsNotNull() do
getDescends(fhGetValueAsLink(ptrChil))
ptrChil:MoveNext("SAME_TAG")
end
ptrFams:MoveNext("SAME_TAG")
end
end
end -- local function getDescends
local function getAnc_Desc(ptrRec) -- Get both Ancestors & Descendants
getAncestor(ptrRec)
dicRelative[fhGetRecordId(ptrRec)] = false -- Remove root Rec Id to allow Descendants
table.remove(arrRelative,1)
getDescends(ptrRec)
end -- local function getAnc_Desc
local function getRelation(ptrRec) -- Get next all Relations generation
if newRelative(ptrRec) then
for _, strFamx in ipairs ({ "~.FAMC"; "~.FAMS"; }) do -- Cater for all parent/spouse families
local ptrFamx = fhGetItemPtr(ptrRec,strFamx)
while ptrFamx:IsNotNull() do
local ptrFam = fhGetValueAsLink(ptrFamx)
local intFam = fhGetRecordId(ptrFam)
if not dicFamilies[intFam] then -- New family Record Id
dicFamilies[intFam] = true
getRelation(fhGetItemPtr(ptrFam,"~.HUSB[1]>")) -- Cater for both sex & same sex partners
getRelation(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
getRelation(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
getRelation(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
local ptrChil = fhGetItemPtr(ptrFam,"~.CHIL") -- Get all children
while ptrChil:IsNotNull() do
getRelation(fhGetValueAsLink(ptrChil))
ptrChil:MoveNext("SAME_TAG")
end
end
ptrFamx:MoveNext("SAME_TAG")
end
end
end
end -- local function getRelation
local ptrRoot = tblRecordList[1]
local isAncestor = strRecordList:match("Ancestor") -- Ancestors or Ancestors & Descendants ?
local isDescends = strRecordList:match("Descendant") -- Descendants or Ancestors & Descendants ?
local isRelation = strRecordList:match("Relation") -- Relations ? i.e. all in same Relation Pool
if isAncestor
and isDescends then getAnc_Desc(ptrRoot)
elseif isAncestor then getAncestor(ptrRoot)
elseif isDescends then getDescends(ptrRoot)
elseif isRelation then getRelation(ptrRoot)
end
table.sort(arrRelative,function(arrA,arrB) return arrA.Id < arrB.Id end)
for intIndi = 1, #arrRelative do
tblRecordList[intIndi] = arrRelative[intIndi].Rec -- Create simple Record list
end
end
progbar.Setup(iup_gui.DialogueAttributes("Bars"))
local intStep = 0
local intIndi = #tblRecordList -- Iterate all from Select Records dialogue
if intIndi >= 500 then
progbar.Start("Searching "..intIndi.." Individuals",intIndi) -- Optionally start the Progress Bar -- V4.3
end
intIndi = 0
return function () -- Iterator function
intStep = intStep + 1
intIndi = intIndi + 1
local ptrIndi = tblRecordList[intIndi]
if intStep > 29 and ptrIndi then
progbar.Message("Record Id "..fhGetRecordId(ptrIndi)) -- Report progress occaisionally
progbar.Step(intStep)
intStep = 0
fhSleep(10,7) -- Prevent FH Stopped Working message
end
if progbar.Stop() then return end
return ptrIndi
end
end -- function TblRecordList
-- Check if Census is missing and Individual was in Census Locality on Census Date
-- Many U.K. & Australia/Canada Census Dates were in same Year, some identical Dates and some different Dates, so must cater for all cases
TblFacts =
{
BIRT=true; BAPM=true; CHR =true; -- V4.3 -- Key Facts to save regardless of Date format
MARR=true; EMIG=true; IMMI=true;
DEAT=true; BURI=true; CREM=true;
Cache={ }; -- V4.3 -- Save the Facts in cache for quick subsequent lookup
}
function GetItemPlace(ptrFact,strTag)
return fhGetItemText(ptrFact,strTag):gsub('".-"',''):lower() -- Exclude "string quoted" place names -- V4.5
end -- function GetItemPlace
function CacheFact(ptrFact,isInLocality) -- Save CENSus and other Facts in the Facts Cache -- V4.8 -- V4.9
local strTag = fhGetTag(ptrFact)
local datDate = fhGetValueAsDate(fhGetItemPtr(ptrFact,"~.DATE"))
if not datDate:IsNull() then -- V4.3 -- Omit any Facts without a Date
local strPlace = GetItemPlace(ptrFact,"~.PLAC") -- V4.0 -- Place case-insensitive match -- V4.5
local strPlac2 = GetItemPlace(ptrFact,"~._PLAC") -- V4.3 -- EMIG/IMMIgration other place -- V4.5
if strTag == "CENS"
or isInLocality and #(strPlace..strPlac2) > 1 and -- Facts for Home/Abroad mode enabled, and Fact has a Place
( TblFacts[strTag] == true or -- V4.3 -- Any key Fact
( datDate:GetType() == "Simple" and -- Any Fact with simple Date, and
datDate:GetSubtype() == "" and -- without a Qualifier, and
#(datDate:GetDisplayText("COMPACT")) > 9 ) -- format is "dd mmm yyyy" with all parts needed, so length is > 9
) then
local intDpt = datDate:GetDatePt1() -- V4.3 -- Obtain Date Point
table.insert(TblFacts.Cache,{ Tag=strTag; Date=datDate:Clone(); Dpt=intDpt; Place=", "..strPlace..", "; Plac2=", "..strPlac2..", "; })
end
end
end -- function CacheFact
function SaveFacts(ptrRec,isInLocality)
for ptrFact in iterate.Facts(ptrRec) do -- Save CENSus and optionally key Facts & all Facts with both full Simple Date and Place fields
CacheFact(ptrFact,isInLocality) -- V4.9
end
local intFact = 1
local ptrFact = fhGetItemPtr(ptrRec,"~.~SHAR>")
while ptrFact:IsNotNull() do -- V4.8 -- Include witnessed facts %INDI.~SHAR[i]>%
CacheFact(ptrFact,isInLocality) -- V4.9
intFact = intFact + 1
ptrFact = fhGetItemPtr(ptrRec,"~.~SHAR["..intFact.."]>")
end
end -- function SaveFacts
function CensusMissing(ptrIndi,tblProfile,intRecId,dicUserOption)
-- ptrIndi Individual of Interest
-- tblProfile Census Profile of Country
-- intRecId Individual Record Id
-- dicUserOption User optional mode settings -- V4.3
-- InLocality Facts for Home/Abroad mode
-- EachCensus Min Age in All Census mode
-- MinimumAge Minimum Age at Census
local isInLocality = dicUserOption.InLocality -- V4.3 -- Facts for Home/Abroad optional mode setting
local isInLocale = true -- Assume person is in Census Locality on Census Date and missing Census Event
local arrLocale = tblProfile.Locale:lower():plain():split() -- V4.0 case-insensitive match
if TblFacts.RecId ~= intRecId then -- Start empty table for each new Record Id
TblFacts.Cache = {}
TblFacts.RecId = intRecId
for intFam, tblFam in ipairs (TblIndividualDetails(ptrIndi,tblProfile,intRecId).Family) do
local ptrFam = tblFam.FamLink -- Save Family CENSus Events, key Facts & all Facts with both full Simple Date and Place fields
if ptrFam and ptrFam:IsNotNull() then SaveFacts(ptrFam,isInLocality) end
end
SaveFacts(ptrIndi,isInLocality) -- Save Individual CENSus Events, key Facts & all Facts with both full Simple Date and Place fields
table.sort( TblFacts.Cache,
function(tblFact1,tblFact2)
local intCompare = tblFact1.Dpt:Compare(tblFact2.Dpt) -- V4.3 -- Sort the Facts by Date Point and if equal ensure CENS first (copes with abnormal dates)
if intCompare == 0 then
return tblFact1.Tag == "CENS" and tblFact2.Tag ~= "CENS" -- V4.3 -- tblFact1.Tag == "CENS" alone fails, as sometimes both tags = "CENS"
end
return intCompare < 0
end
)
end
for intFact, tblFact in ipairs(TblFacts.Cache) do -- Check each Fact in lookup table cache
local strTag = tblFact.Tag
local strPlace = tblFact.Place
local intCompare = tblFact.Date:Compare(tblProfile.Date) -- Comparison = 0 even if only partial match for CENSus Event Year or Month & Year
if intCompare == 0 then -- Fact Date matches Census Date
if strTag == "CENS" and strPlace == ", , " then -- CENSus Event for Date but no Place field
return false -- Assume it is for Census Locality, so Census is NOT missing
end
isInLocale = false -- Assume person is NOT in Census Locality on Census Date and Census is NOT missing
for _, strLocale in ipairs ( arrLocale ) do -- V4.0
if strPlace:match(", -"..strLocale.." -, -") then -- V4.0 -- Space tolerant match for Census locale
if strTag == "CENS" then return false end -- CENSus Event for Date & Locality, so Census is NOT missing
isInLocale = true -- Another Fact for Date & Locality, so Census is missing
break
end
end
elseif isInLocality then -- V4.3 -- Check all cached facts (Test against John Smith & Julia Fish, et al)
if ( intCompare < 0 and strTag == "EMIG" )
or ( intCompare > 0 and strTag == "IMMI" ) then -- Adjust at home place name for EMIGration and IMMIgration events
strPlace = tblFact.Plac2
end
isInLocale = false -- Assume person is NOT in Census Locality on Census Date and Census is NOT missing
for _, strLocale in ipairs ( arrLocale ) do
if strPlace:match(", -"..strLocale.." -, -") then -- Space tolerant match for EMIG/IMMI to Locality before Census, or EMIG/IMMI from Locality after Census, or other Fact in Locality
isInLocale = true -- So person is at home and Census may be missing
break
end
end
if intCompare > 0 then break end -- V4.3 -- Fact Date is after Census Date, so skip later facts
end
end
if dicUserOption.EachCensus -- Apply the Minimum Age threshold unconditionally
or not tblProfile.Child then -- or if no Children are in current Census
local tblDetails = TblIndividualDetails(ptrIndi,tblProfile,intRecId)
if tblDetails.Age < dicUserOption.MinimumAge then return false end -- If younger than Minimum Age then Census is NOT missing
end
return isInLocale -- If in Locality on Date then Census is missing, otherwise Census is NOT missing
end -- function CensusMissing
-- Delete files accessed more than 1 hour ago --
function DeleteOldFiles(strFolder,strFile)
-- strFolder Folder of Files
-- strFile Filename to Delete
local intTime = os.time() - 3600 -- Time in seconds one hour ago
for strEntry in lfs.dir(strFolder) do -- Search for files in the folder
if strEntry ~= "." and strEntry ~= ".." then
local strPath = strFolder..strEntry
local tblAttr, strError = lfs.attributes(strPath) -- Obtain file attributes
if not tblAttr then tblAttr = { mode="attrfail"; error=strError; } end
if tblAttr.mode == "file"
and strEntry:matches(strFile) then -- Simple file with matching name?
if tblAttr.access < intTime then
general.DeleteFile(strPath) -- Delete if access time is an hour ago
end
end
end
end
end -- function DeleteOldFiles
-- Remove [brackets] and turn punctuation into spaces before splitting to count components -- V4.1
function StrShortName(strName)
if #(strName:gsub("%[.-%]",""):gsub("[!-/:-@%[\\%]^_`{|}~]"," "):split(" ")) <= 1 then return "" end
return strName
end -- function StrShortName
local tblFormatName = {}
-- Format Individual Name and Record ID --
function StrFormatName(ptrIndi,strRef,tblProfile,intRecId,isShortNames)
-- ptrIndi Individual of Interest
-- strRef Data Ref of Relative
-- tblProfile Census Profile of Country
-- intRecId Record Id optional
-- isShortNames Chosen Fewer than 2 Name components mode -- V4.1
ptrIndi = fhGetItemPtr(ptrIndi,strRef)
if ptrIndi:IsNull() then return "", "", ptrIndi end -- V4.9
local tblDetails = TblIndividualDetails(ptrIndi,tblProfile,intRecId) -- Obtain name at Census Date if supplied, otherwise birth name
local strNAME = tblDetails.SNAME..", "..tblDetails.GIVEN.." ["..tblDetails.RecId.."]" -- V4.9
local strName = tblDetails.sname..", "..tblDetails.given.." ["..tblDetails.RecId.."]"
if isShortNames then -- if name too short then reduce to empty string and remember -- V4.1
tblFormatName[strName] = tblFormatName[strName] or StrShortName(strName)
strName = tblFormatName[strName]
end
return strName, strNAME, ptrIndi -- V4.9
end -- function StrFormatName
-- Run Census Lookup --
function RunLookup(strCensusList,dicLookupSite,strRecordList,dicUserOption)
-- strCensusList Chosen set of Census entries
-- dicLookupSite Chosen lookup web site details -- V4.3
-- strRecordList Chosen Record selection mode
-- dicUserOption Chosen user optional settings :- -- V4.3
-- PartCensus Chosen Partial Census Records mode -- V4.1
-- MissCensus Chosen Missing Census Records mode -- V4.3
-- ShortNames Chosen Fewer than 2 Name components mode -- V4.1
-- InLocality Chosen Facts for Home/Abroad mode
-- MinimumAge Chosen Minimum Age at Census
-- EachCensus Chosen Min Age in All Census mode
-- Polygamous Chosen Polygamous relationships mode -- V4.3
local strFolder = iup_gui.MachinePath.."\\" -- Define the folder for the browser files
local strPlugin = iup_gui.Plugin:lower():gsub(" ","") -- Define lowercase and despaced Plugin name
local strFile_js = strPlugin..".js" -- Define filenames for .css, .js & .html files
local strFile_css = strPlugin..".css"
local strFile_html = strPlugin..os.tmpname():gsub("^.*\\","-")..".html"-- Lua 5.1 and Lua 5.3 compatible -- V4.5
DeleteOldFiles(strFolder,strPlugin) -- Delete any old copies of the browser files
local putWebPage = general.OpenFile(strFolder..strFile_html,"wb") -- Web page output HTML file -- V4.3
local strWebPage = StrWebPageTemplate -- Web page of headings and placeholders for HTML components
local strWebLine = "" -- Web line in HTML grid
local intWebLine = 0 -- Web line number -- V4.3
local strLineColour = "" -- Web line colour switch
local tblProfile = TblCensusProfile(strCensusList,dicLookupSite,dicUserOption) -- Table of Census data per year for chosen country -- V4.1
local strTitleName = "selected Individuals" -- Title name for Individuals via Select Records
local ptrRecordRoot = fhNewItemPtr() -- Root person for Relationship in Result Set
local tblRecordList = {} -- List of Individual Records to search
local intRecordList = 20000 -- Allow large numer of Individuals via Select Records
if strRecordList then intRecordList = 1 end -- But only one for Ancestors/Descendants/Relations option
tblRecordList = fhPromptUserForRecordSel("INDI",intRecordList) -- Select Records dialogue
if #tblRecordList == 0 then return end
local ptrRoot = tblRecordList[1]:Clone()
if strRecordList then -- Title details of chosen root person for Ancestors/Descendants/Relations option
strTitleName = strRecordList..fhGetItemText(ptrRoot,"~.NAME").." ["..fhGetRecordId(ptrRoot).."] "..fhCallBuiltInFunction("LifeDates",ptrRoot) -- V4.1
end
ptrRecordRoot = fhCallBuiltInFunction("FileRoot"):Clone() -- Use FileRoot to avoid slow fhCallBuiltInFunction("Relationship/Code"...) functions -- V4.3
if ptrRecordRoot:IsNull() then ptrRecordRoot = ptrRoot:Clone() end
strTitleName = strCensusList.." are missing for "..strTitleName -- Prefix Census List to title
strWebPage = strWebPage:replace("{JS_File}",strFile_js) -- Insert web page components into placeholders
strWebPage = strWebPage:replace("{CSS_File}",strFile_css)
strWebPage = strWebPage:replace("{TitleName}",encoder.StrEncode_XML(strTitleName))
for strLookupName, strWebAddress in pairs (dicLookupSite) do -- V4.3 -- Build heading for each lookup web site
strWebPage = strWebPage:replace("{"..strLookupName.."}",StrBuildLookup(strWebAddress))
end
putWebPage:write(strWebPage) -- Write HTML header -- V4.3
local tblPosition = {} -- Result Set tables -- V4.1
local tblIndiName = {}
local tblIndiItem = {}
local tblRecordId = {}
local tblLifeDate = {}
local tblFactItem = {}
local tblGenApart = {}
local tblRelation = {}
local tblFathName = {}
local tblFathItem = {}
local tblMothName = {}
local tblMothItem = {}
for ptrIndi in TblRecordList(tblRecordList,strRecordList) do -- Check each Individual against each Census -- V4.3
local datBirthDate = fhNewDate()
local datDeathDate = fhNewDate()
local strPosition = ""
local intRecordId = fhGetRecordId(ptrIndi)
local strBirthYear = ""
datBirthDate:SetSimpleDate(general.EstimatedBirthDates(ptrIndi,9).Min) -- V4.1 -- Fix erroneous EstimatedBirthDate function
datDeathDate:SetSimpleDate(general.EstimatedDeathDates(ptrIndi,9).Max) -- V4.3 -- Fix erroneous EstimatedDeathDate function
for intEntry, tblProfile in ipairs(tblProfile) do -- Check each Census against life dates of Individual
local datCensusDate = tblProfile.Date
if datBirthDate:Compare(datCensusDate) <= 0
and datDeathDate:Compare(datCensusDate) >= 0 then -- Individual is alive at selected Census Date
-- Check if Census Event is missing and Individual is in Census Locality on Census Date
if CensusMissing(ptrIndi,tblProfile,intRecordId,dicUserOption) then
local tblDetails = TblIndividualDetails(ptrIndi,tblProfile,intRecordId)
local strIndiName, strIndiNAME = StrFormatName(ptrIndi,"~",tblProfile,intRecordId,dicUserOption.ShortNames) -- V4.9
if #strIndiName > 0 then -- Omit if too few Primary Name components? -- V4.1
local strFathName, strFathNAME, ptrFath = StrFormatName(ptrIndi,"~.~FATH>") -- V4.9
local strMothName, strMothNAME, ptrMoth = StrFormatName(ptrIndi,"~.~MOTH>") -- V4.9
local intBirthYear = tblDetails.Birth -- Census is Missing and Individual alive in Census Locality
if intBirthYear > 0 then -- V4.2
strBirthYear = encoder.StrUTF8_XML(tostring(intBirthYear)..encoder.StrANSI_UTF8(" ± ")..tblDetails.Range)
end
if strLineColour == "odd" then strLineColour = "even" else strLineColour = "odd" end
intWebLine = intWebLine + 1 -- V4.3
strWebLine = StrWebLineTemplate -- Replace web line placeholders with actual components below
strWebLine = strWebLine:replace("{LineColour}",strLineColour)
strWebLine = strWebLine:replace("{ItemNumber}",intWebLine) -- V4.3
for strLookupName, strWebAddress in pairs (dicLookupSite) do -- V4.3 -- Build search cell for each lookup web site
strWebLine = strWebLine:replace("{"..strLookupName.."}",StrBuildLookup(strWebAddress,tblProfile,strLookupName,ptrIndi,tblDetails))
end
strWebLine = strWebLine:replace("{Individual}",strIndiName) -- V4.1
strWebLine = strWebLine:replace("{Birth_Year}",strBirthYear)
strWebLine = strWebLine:replace("{Life_Dates}",tblDetails.Dates)
strWebLine = strWebLine:replace("{FamilyName}",encoder.StrGetItem_XML(tblDetails.FamLink,"~"))
strWebLine = strWebLine:replace("{FamilyDate}",tblDetails.FamDate)
strWebLine = strWebLine:replace("{FatherName}",strFathName)
strWebLine = strWebLine:replace("{MotherName}",strMothName)
putWebPage:write(strWebLine) -- Write grid line to HTML file -- V4.3
if strIndiNAME ~= tblIndiName[#tblIndiName] then -- Census Missing for new Individual Name so update Result Set -- V4.1 -- V4.9
strPosition = string.rep( " ", 4 - string.len(intWebLine) )..intWebLine -- V4.3
table.insert(tblPosition,strPosition)
table.insert(tblIndiName,strIndiNAME) -- V4.9
table.insert(tblIndiItem,ptrIndi:Clone())
table.insert(tblRecordId,intRecordId)
table.insert(tblLifeDate,( tblDetails.Dates:gsub(" "," ")) )
table.insert(tblGenApart,fhCallBuiltInFunction("RelationCode",ptrRecordRoot,ptrIndi,"GENERATION",1) or 99)
table.insert(tblRelation,fhCallBuiltInFunction("Relationship",ptrRecordRoot,ptrIndi,"TEXT",1))
table.insert(tblFathName,strFathNAME) -- V4.9
table.insert(tblFathItem,ptrFath:Clone())
table.insert(tblMothName,strMothNAME) -- V4.9
table.insert(tblMothItem,ptrMoth:Clone())
else
tblPosition[#tblPosition] = strPosition.." - "..intWebLine -- V4.3
end
end
end
end
end
end
if #tblIndiItem > 0 then -- Invoke browser web page and show Result Set
general.SaveStringToFile(StrWebPageJS,strFolder..strFile_js) -- Output the .js Java Script and .css Style Sheet
general.SaveStringToFile(StrWebPageCSS,strFolder..strFile_css)
putWebPage:write(StrWebTailTemplate)
putWebPage:close() -- Close and execute HTML output file -- V4.3
local isOK, intErrorCode, strErrorText = fhShellExecute(strFolder..strFile_html)
progbar.Step(1)
fhOutputResultSetTitles(strTitleName, strTitleName, "Printed Date: %#x")
fhOutputResultSetColumn("No." , "text" , tblPosition, #tblIndiItem, 50, "align_left" ) -- V4.1
fhOutputResultSetColumn("Individual", "text" , tblIndiName, #tblIndiItem, 140, "align_left")
fhOutputResultSetColumn("Individual", "item" , tblIndiItem, #tblIndiItem, 140, "align_left", 0, true, "default", "buddy" ) -- V4.1
fhOutputResultSetColumn("Rec Id" , "integer", tblRecordId, #tblIndiItem, 30, "align_mid" )
fhOutputResultSetColumn("Life Dates", "text" , tblLifeDate, #tblIndiItem, 50, "align_left") -- V4.1
fhOutputResultSetColumn("Gen. Gap" , "integer", tblGenApart, #tblIndiItem, 40, "align_mid" ) -- V4.1
local strColumn = "Relationship to "..fhGetDisplayText(ptrRecordRoot).." ["..fhGetRecordId(ptrRecordRoot).."]"
fhOutputResultSetColumn(strColumn , "text" , tblRelation, #tblIndiItem, 180, "align_left") -- V4.1
fhOutputResultSetColumn("Father" , "text" , tblFathName, #tblIndiItem, 140, "align_left")
fhOutputResultSetColumn("Father" , "item" , tblFathItem, #tblIndiItem, 140, "align_left", 0, true, "default", "buddy" ) -- V4.1
fhOutputResultSetColumn("Mother" , "text" , tblMothName, #tblIndiItem, 140, "align_left")
fhOutputResultSetColumn("Mother" , "item" , tblMothItem, #tblIndiItem, 140, "align_left", 0, true, "default", "buddy" ) -- V4.1
progbar.Close()
else
progbar.Close()
if #tblRecordList > 0 then
iup_gui.MemoDialogue("\n No "..strTitleName..". \n") -- No Census Missing informative message
end
end
end -- function RunLookup
-- Main code starts here --
fhInitialise(5,0,8,"save_recommended") -- Recommend current GEDCOM is saved if outstanding changes
PresetGlobalData() -- Preset global data definitions
ResetDefaultSettings() -- Preset default sticky settings
LoadSettings() -- Load sticky data settings
GUI_MainDialogue() -- Invoke User Dialogue
SaveSettings() -- Save sticky data settings
--[[
@TBD: Add USA State Census 1**5 years (https://en.wikipedia.org/wiki/State_censuses_in_the_United_States); Consider WWI USA Draft Registration Cards;
@V5.3: Library V3.9; Check for Updates button; Centre window on Parent FH window;
@V5.2: Added Canada.ca Library & Archives;
@V5.1: Added Ancestry UK 1921 and 1939 Census; Updated library to v3.7;
@V5.0: Fix CurrentFamily(...) spouse maiden name error; Updated library to v3.4;
@V4.9: Fix CacheFact(...) isInLocality bug; Fix name format in Result Set; Add Deutschland to Germany, Danmark to Denmark, Norge to Norway;
@V4.8: Check witnessed facts via INDI.~SHAR[i]>;
@V4.7: Updated library to Functions Prototypes v3.3; Updated for UK 1921 & USA 1950 Census; Updated various Census entries & URL templates; List all census records checked on a Help & Advice page;
@V4.6: Updated FamilySearch URL templates;
@V4.5: Updated library to Functions Prototypes v3.0; iup_gui.Balloon = "NO" for PlayOnLinux/Mac; Exclude "string quoted" place names; FH V7 Lua 3.5 IUP 3.28;
@V4.4: Updated library module for EstimatedDeathDates().
@V4.3: New TblRecordList() iteration loop, CensusMissing() new checks for home/abroad, cater for polygamy, new btnRedisplay, add Germany & Norway, cater for census dates not online, recognise Isle of Man & Channel Islands, updated library module for safer IUP GUI and EstimatedBirth/DeathDates().
@V4.2: Revised Ancestry URL USA Census codes, and USA & Canada Gender codes, plus FamilySearch and MyHeritage web sites added, use mid Age instead of max Age for tests, revised Template design for new StrBuildLookup().
@V4.1: Inhibit dialogue while running lookup, EstimatedBirthDates() for =EstimatedBirthDate() errors, DateExists() for Date Phrase no Date, exclude Short Names option, exclude partial Census Records option, extra Result Set columns, all Given names to FMP, latest Library.
@V4.0: CensusMissing() tolerant of Place case & spaces, cater for Null date, include Family Facts & sort Facts to curtail search unconditionally, include Record Id in browser page heading, fixed sorting with space => .
@V3.9: Add the 1939 Register of England&Wales for findmypast, and fix various other Relationship errors mainly in CurrentFamily() and StrRelatives().
@V3.8: Removed ".new" from "findmypast.co.uk".
@V3.7: Revised general & encoder library modules.
@V3.6: Both ANSI FH V5 & UTF-8 FH V6 IUP 3.11.2 encoder_v3, each encoder.StrANSI_XML/URI becomes encoder.StrEncode_XML/URI, and os.getenv("TEMP") becomes iup_gui.MachinePath.
@V3.5: Add BalloonToggle() right-click Set Window Fonts, search all Events if few Individuals, add Minimum Age threshold option for Child=false or EachCensus toggle,
@V3.5: added UK 1801/1821 Dartford Kent, USA 1890 Civil War Veterans Schedule, Ireland 1766, Canada 1825/1842 & 1906/1916 restricted to Manitoba, Saskatchewan, and Alberta.
@V3.4: Reinstate married women's surname of husband, make TblIndividualDetails() more efficient.
@V3.3: Fix droplist tooltips for XP, add UK Census 1790 & 1831 and Ireland Census 1790 to 1911, TblCensusProfile uses preset tables, better Census link labels, all columns sortable, all names use same format, fix home or abroad bugs.
@V3.2: Only add spouse if married and add parents if unmarried & younger than 21, conditionally add these family members & birth details & gender to findmypast searches,
@V3.2: use findmypast world search 'A-Z of Record Sets' mode, add 'year n/a' legend where Census unavailable instead of disabling buttons so useful list is always provided.
@V3.1: iup_gui module Help window X Close crash fix, extra fonts, new CheckVersionInStore, inhibit Minimize & Maximize in popups, new.findmypast.co.uk web search URL, etc, etc.
@V3.0: Optionally do not list as missing from Census if a Fact is found on the Census Date but not in the Census Locality, and cope with different country Census dates in same year.
@V2.9: Uses IsRelativeOf() as workaround for RelationPool() not working, plus related minor change to GUI, Version History help, Canadian 1921 Census, and new Library Modules.
@V2.8: Mother with a child, but father with no surname, retains original surname, and Family Date bug corrected.
@V2.7: EstimatedBirthDate() sets Birth Year & Range in search templates, Individual Name & Birth lookup more efficient, new FindMyPast Template codes,
new XML/HTML/URI/UTF encoding, DeleteOldFiles() modified, gsub() problems fixed with plain text versions, and revised web page layout.
@V2.6: Create js & css files from scripts in Plugin and delete 1-hour old temp js & css & html files, add FindMyPast USA, Ireland & Australia World Search for UK & USA Census.
@V2.5: Add 'Restore Defaults' and 'Set Window Font' and 'Help & Advice' and 'Relationship Pool' option, plus Australian Census, and all Ancestry web sites.
@ FHUG Work in Progress Versions:
@V2.4: Set GUI attributes according to options, remove gss= Mask for USA Ancestry, add option for Ancestors/Descendants/both/anyone.
@V2.3: Changed Canada Mask settings and many other minor updates to improve HTML and LUA style.
@V2.2: Plugin exits after Run Census Lookup, and HTML symbols are escaped.
@V2.1: Added Canada 1851, and incorporated Bill Henshaw's USA Census settings
@V2.0: Merge various similar Plugins into one with user options
@ Ancestor's UK Census Checker Versions:
@V1.3: Add Output to Result Set as well, to make it easier to find the Ancestors in the Local Database and support custom Census events in the xxxx Census naming format, additionally add sorting to the web output.
@V1.2: Change Find My Past Search to the new format
@V1.1: Added Find My Past Search
--]] Source:Lookup-Missing-Census-Facts-11.fh_lua