Check Sort Dates.fh_lua

--[[
@Title: Check Sort Dates
@Type: Standard
@Author: Mark Draper
@Version: 1.0
@LastUpdated: 2 Sep 2022 
@Licence: This plugin is copyright (c) 2022 Mark Draper, and is licensed under the MIT License
which is hereby incorporated by reference (see https://pluginstore.family-historian.co.uk/fh-plugin-licence)
@Description: Lists and optionally deletes sort dates that are either invalid or unnecessary
]]

fhInitialise(7, 0, 0,'save_required')
require('iuplua')
require 'luacom'
iup.SetGlobal('CUSTOMQUITMESSAGE','YES')
iup.SetGlobal('UTF8MODE','YES')
iup.SetGlobal('UTF8MODE_FILE','YES')

function main()

	local selection

	-- set default IUP font

	local Registry = 'HKEY_CURRENT_USER\\Software\\Calico Pie\\Family Historian\\2.0\\'
	local k = getRegKey(Registry .. 'Preferences\\PDX Font')
	local scaling = getRegKey(Registry .. 'Preferences\\Font Scaling Percent') or 100

	local tblK = ParseString(k)
	local font = tblK[14]
	local size = tblK[1] * scaling / 100

 	iup.SetGlobal('DEFAULTFONT',font .. ' ' .. size//20)

	-- create menu

	local Title = iup.label{title='Select option to check project sort dates',padding='10x10'}
	local btnSortDates = iup.button{title='Check sort dates',size='80x15'}
	local btnHelp = iup.button{title='Help'}
	local btnClose = iup.button{title='Close'}

	local Buttons = iup.vbox{btnSortDates,btnHelp,btnClose;
			normalizesize='BOTH',gap=10}
	local vbox = iup.vbox{Title,Buttons;alignment='ACENTER',gap=10;margin='10x10'}
	local dialog = iup.dialog{vbox; resize='No', minbox='No', maxbox='No', border='NO',
			 title='Check Sort Dates (1.0)'}

	function btnSortDates:action()
		selection = 1
		return iup.CLOSE
	end

	function btnHelp:action()
		selection = 2
		return iup.CLOSE
	end

	function btnClose:action()
		selection = 0
		return iup.CLOSE
	end

	repeat
		selection = 0		

		dialog:show()
		iup.MainLoop()
		dialog:hide()

		if selection == 1 then
			selection = SortDates()
		elseif selection == 2 then
			local Cmd = 'https://pluginstore.family-historian.co.uk/page/help/check-sort-dates'
			fhShellExecute(Cmd)
			fhSleep(1000) 			-- slight pause to suspend immediate redraw
		end 
	until not selection or selection == 0
end

-- ************************************************************************** --

function SortDates()

	-- deletes all invalid sort dates (those that do not resolve as dates) and superfluous sort dates
	-- (equal to actual date or start of range)

	local p = fhNewItemPtr()
	local pR = fhNewItemPtr()
	local tblX = {}
	local CountI, CountS = 0, 0

	for _, Type in ipairs({'INDI', 'FAM'}) do
		pR:MoveToFirstRecord(Type)
		while pR:IsNotNull() do
			p:MoveToFirstChildItem(pR)
			while p:IsNotNull() do
				local pD = fhGetItemPtr(p, '~.DATE')
				local pSD = fhGetItemPtr(p, '~._SDATE')
				if pSD:IsNotNull() then
					local dtSDate = fhGetValueAsDate(pSD)
					if dtSDate:IsNull() then					-- invalid date
						table.insert(tblX, pSD:Clone())
						CountI = CountI + 1
					else
						local dtDate = fhGetValueAsDate(pD)
						local dpDate = dtDate:GetDatePt1()
						local dpSDate = dtSDate:GetDatePt1()
						if dpDate:Compare(dpSDate) == 0 then
							table.insert(tblX, pSD:Clone())
							CountS = CountS + 1
						end
					end
				end					
				p:MoveNext()
			end
			pR:MoveNext()
		end
	end

	if #tblX == 0 then
		fhMessageBox('No invalid or superfluous sort dates identified.', 'MB_OK', 'MB_ICONINFORMATION')
		return true
	end

	local msg = CountI .. ' invalid and ' .. CountS .. ' superfluous sort dates were identified.\n'
	if #tblX == 1 then
		msg = msg .. 'What do you want to do with it?'
	else
		msg = msg .. 'What do you want to do with them?'
	end
	local Action = iup.Alarm('Process Sort Dates', msg, 'List', 'Delete', 'Cancel')
	if Action == 3 then
		return true
	elseif Action == 2 then
		for _, p in ipairs(tblX) do fhDeleteItem(p) end
		fhUpdateDisplay()
		return true
	elseif Action == 1 then
		local tblXiE = {}
		local tblXiED = {}
		local tblXiR = {}
		local tblXSD = {}
		for _, p in ipairs(tblX) do
			local pE = fhNewItemPtr()
			pE:MoveToParentItem(p)
			local pED = fhGetItemPtr(pE, '~.DATE')
			local pR = fhNewItemPtr()
			pR:MoveToParentItem(pE)
			table.insert(tblXiE, pE:Clone())
			table.insert(tblXiED, pED:Clone())
			table.insert(tblXiR, pR:Clone())
			table.insert(tblXSD, p:Clone())
		end	
		fhOutputResultSetTitles('Sort Dates')
		fhOutputResultSetColumn('Record', 'item', tblXiR, #tblXiR,200,'align_left', 0, true, 'default')
		fhOutputResultSetColumn('Fact', 'item', tblXiE, #tblXiE,175,'align_left', 0, true, 'default')
		fhOutputResultSetColumn('Date', 'item', tblXiED, #tblXiED,160,'align_left', 0, true, 'date')
		fhOutputResultSetColumn('Sort Date', 'item', tblXSD, #tblXSD,80,'align_left', 0, true, 'date')
	end
end

-- ************************************************************************** --

function getRegKey(key)
    local sh = luacom.CreateObject 'WScript.Shell'
    local ans
    if pcall(function () ans = sh:RegRead(key) end) then
      return ans
    else
      return nil,true
    end
end

-- ************************************************************************** --

function ParseString(S)

	-- splits a delimited string into a table without using stringx library

	local tblT = {}

	while true do
		local i = S:find(',')
		if not i then							--	no more delimiters
			table.insert(tblT, S)
			break
		end
		table.insert(tblT, S:sub(1,i-1))
		S = S:sub(i+1)
	end
	return tblT
end

-- ************************************************************************** --

main()

Source:Check-Sort-Dates-1.fh_lua