Replace Selected Source Citations.fh_lua

--[[
@Title:			Replace Selected Source Citations
@Type:				Standard
@Author:			Mike Tate
@Contributors:	
@Version:			1.2
@Keywords:		
@LastUpdated:	19 Dec 2020
@Licence:			This plugin is copyright (c) 2020 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:	Replace one Source Citation with another for chosen Citation criteria conditionally.
@V1.2:				Confirm replacements; Root Record & Rec Id in Result Set; FH V7 Lua 3.5 IUP 3.28;
@V1.1:				Minor update;
@V1.0:				First published in Plugin Store;
]]

local strPluginName = "Replace Selected Source Citations  1.2"				-- Update title and version number here

require("iuplua")																		-- To access GUI window builder

if fhGetAppVersion() > 5 then														-- Cater for Unicode UTF-8 from FH Version 6 onwards
	fhSetStringEncoding("UTF-8")
	iup.SetGlobal("UTF8MODE","YES")
	iup.SetGlobal("UTF8MODE_FILE","NO")
	iup.SetGlobal("CUSTOMQUITMESSAGE","YES")										-- Needed for IUP 3.28
end
if fhGetAppVersion() > 6 then unpack = table.unpack end						-- Needed for Lua 5.3

function matches(strTxt,strFind,intInit)											-- matches is plain text version of string.match()
	local strMagic = "([%^%$%(%)%%%.%[%]%*%+%-%?])"								-- UTF-8 replacement for "(%W)"
	strFind = tostring(strFind or ""):gsub(strMagic,"%%%1")					-- Hide magic pattern symbols
	return tostring(strTxt or ""):match(strFind,tonumber(intInit))
end -- function matches

function GetRecord(index)																-- Get old/new Source record choice
	local ptrGet = nil
	local strGet = ""
	if index == -1 then
		local tblGet = fhPromptUserForRecordSel("SOUR",1)						-- Prompt for Source record
		if #tblGet == 1 then
			ptrGet = tblGet[1]															-- Link to record
			strGet = fhGetDisplayText(ptrGet)										-- Name of record
		end
	end
	return ptrGet,strGet
end -- function GetRecord()

function GetOld(dialog,index)
	if index == iup.GETPARAM_MAP then												-- Correct button labels needed for IUP 3.28 bug	-- V1.2
		dialog.Button1.Title = "Select Old Source"
		dialog.Button2.Title = "Cancel Plugin"
	end
	ptrOld,strOld = GetRecord(index)												-- Prompt for new Source record link & name
	return 1
end -- function GetOld

function GetNew(dialog,index)
	if index == iup.GETPARAM_MAP then												-- Correct button labels needed for IUP 3.28 bug	-- V1.2
		dialog.Button1.Title = "Select New Source"
		dialog.Button2.Title = "Cancel Plugin"
	end
	ptrNew,strNew = GetRecord(index)												-- Prompt for new Source record link & name
	return 1
end -- function GetNew

function GetOpt(dialog,index)
	if index == iup.GETPARAM_MAP then												-- Correct button labels needed for IUP 3.28 bug	-- V1.2
		dialog.Button1.Title = "Perform Replace"
		dialog.Button2.Title = "Cancel Plugin"
	end
	return 1
end -- function paramAction

function Main()

	if not iup.GetParam("Select Old Source Record",GetOld," Select the Source record that needs to be replaced.%t\n%u[Select Old Source,Cancel Plugin]")
	or not ptrOld then return end

	if not iup.GetParam("Select New Source Record",GetNew," Select the Source record to replace that old Source.%t\n%u[Select New Source,Cancel Plugin]")
	or not ptrNew then return end

	local tblForm = {}																	-- Format strings for iup.GetParam
	local tblData = {}																	-- Parameter values for iup.GetParam
	local tblTags = {}																	-- Data Ref Tags for Citation fields
	local tblDel  = {}																	-- Indices for delete & contains actions
	local tblCon  = {}
	local intQuay = 0																	-- Cater for 'Assessment' field
	local arrQuay = {[0]="","Unreliable","Questionable","Secondary evidence","Primary evidence"}
	local strQuay = "| "..table.concat(arrQuay,"|",0).."|"						-- GetParam list is 0 based 
	local intRepl = 0
	local strRepl = "Yes"																-- Replacement confirmation	-- V1.2

	local function setTables(strForm,strData,strTags,tblAct)					-- Set those lookup table to values below
		table.insert(tblForm,strForm)
		table.insert(tblData,strData)
		table.insert(tblTags,strTags)
		if tblAct then table.insert(tblAct,#tblForm) end						-- List of indices for delete or contains actions
		if intQuay == 0 and matches(strTags,"QUAY") then intQuay = #tblData end
		if matches(strTags,"????") then intRepl = #tblData end 				-- V1.2
	end -- local function setTables

	setTables( "Old Source to be replaced: %s"          , strOld , " " )
	setTables( "'Assessment' field contains?%l"..strQuay, 0      , "~.QUAY"      , tblCon )
	setTables( "'Where within Source' contains: %s"     , ""     , "~.PAGE"      , tblCon )
	setTables( "'Text From Source' contains: %m"        , ""     , "~.DATA.TEXT" , tblCon )
	setTables( "Local 'Note' field contains: %m"        , ""     , "~.NOTE2"     , tblCon )
	setTables( "If all conditions above match:   %t"    )
	setTables( "New Source as replacement: %s"          , strNew , " " )
	setTables( "Delete 'Entry Date' field? %b"          , 0      , "~.DATA.DATE" , tblDel )
	setTables( "Delete 'Assessment' field? %b"          , 0      , "~.QUAY"      , tblDel )
	setTables( "Delete 'Where within Source'? %b"       , 0      , "~.PAGE"      , tblDel )
	setTables( "Delete 'Text From Source'? %b"          , 0      , "~.DATA.TEXT" , tblDel )
	setTables( "Delete local 'Note' field? %b"          , 0      , "~.NOTE2"     , tblDel )
	setTables( "Confirm each replacement? %b"           , 0      , "????" )	-- V1.2
	setTables( "%u[Perform Replace, Cancel Plugin]"     )

	tblData = { iup.GetParam("Source Citation Options",GetOpt,table.concat(tblForm,"\n"),unpack(tblData)) }
	if not tblData[1] then return end
	table.remove(tblData,1)																-- Align data with tables above
	tblData[intQuay] = arrQuay[tblData[intQuay]]										-- Convert 'Assessment' index to text

	local tblRec  = {}																		-- Result Set tables	-- V1.2
	local tblRid  = {}
	local tblRoot = {}
	local tblItem = {}
	local tblDate = {}
	local tblQuay = {}
	local tblPage = {}
	local tblText = {}
	local tblNote = {}
	local tblType = {"INDI","FAM","NOTE","SOUR","REPO","SUBM","OBJE","_PLAC"}	-- Record type tags with Citations
	local ptrItem = fhNewItemPtr()

	for intType, strType in ipairs(tblType) do										-- Scan all record Types that may have Citations
		ptrItem:MoveToFirstRecord(strType)
		while ptrItem:IsNotNull() do
			if fhGetTag(ptrItem) == "SOUR"												-- Search items until Source Citation found
			and fhHasParentItem(ptrItem) then
				if ptrOld:IsSame(fhGetValueAsLink(ptrItem)) then					-- Cited Source record matches
					local isCon = true
					for _, intCon in ipairs (tblCon) do
						if not matches(fhGetItemText(ptrItem,tblTags[intCon]),tblData[intCon]) then
							isCon = false													-- Field does not contain required text
							break
						end
					end
					if isCon then															-- All fields also match
						local ptrRoot = fhNewItemPtr()
						ptrRoot:MoveToParentItem(ptrItem)								-- Obtain root for Citation	-- V1.2
						local strRoot = fhGetDisplayText(ptrRoot)
						ptrRoot:MoveToRecordItem(ptrItem)								-- Obtain record of Citation	-- V1.2
						local strRecd = fhGetDisplayText(ptrRoot)
						if tblData[intRepl] > 0 then										-- Confirm each replacement?	-- V1.2
							strRepl = fhMessageBox("\nIn record "..strRecd.." \ndo you want to replace Source Citation for \n"..strRoot,"MB_YESNOCANCEL")
						end
						if strRepl == "Yes" then
							local isOK = fhSetValueAsLink(ptrItem,ptrNew)			-- So replace this record with new Source record
							for _, intDel in ipairs (tblDel) do							-- Delete chosen fields
								if tblData[intDel] == 1 then
									local isOK = fhDeleteItem(fhGetItemPtr(ptrItem,tblTags[intDel]))
								end
							end
							table.insert(tblRec ,ptrRoot:Clone())						-- Update Result Set	-- V1.2
							table.insert(tblRid ,fhGetRecordId(ptrRoot))
							table.insert(tblRoot,strRoot)
							table.insert(tblItem,ptrItem:Clone())
							table.insert(tblDate,fhGetItemPtr(ptrItem,"~.DATA.DATE"))
							table.insert(tblQuay,fhGetItemPtr(ptrItem,"~.QUAY"))
							table.insert(tblPage,fhGetItemPtr(ptrItem,"~.PAGE"))
							table.insert(tblText,fhGetItemPtr(ptrItem,"~.DATA.TEXT"))
							table.insert(tblNote,fhGetItemPtr(ptrItem,"~.NOTE2"))
						end
					end
				end
			end
			ptrItem:MoveNextSpecial()
			if strRepl == "Cancel" then break end
		end
		if strRepl == "Cancel" then break end
	end
	if #tblItem > 0 then
		fhOutputResultSetTitles( strPluginName )																	-- V1.2
		fhOutputResultSetColumn("Root Record "       , "item", tblRec , #tblItem, 180, "align_left")	-- V1.2
		fhOutputResultSetColumn("Rec Id"          , "integer", tblRid , #tblItem,  30, "align_mid" )	-- V1.2
		fhOutputResultSetColumn("Citation Root"      , "text", tblRoot, #tblItem, 180, "align_left")
		fhOutputResultSetColumn("Source Buddy"       , "item", tblItem, #tblItem, 180, "align_left", 0, true, "default", "buddy")
		fhOutputResultSetColumn("Entry Date"         , "item", tblDate, #tblItem,  60, "align_left")
		fhOutputResultSetColumn("Assessment"         , "item", tblQuay, #tblItem,  60, "align_left")
		fhOutputResultSetColumn("Where within Source", "item", tblPage, #tblItem, 180, "align_left")
		fhOutputResultSetColumn("Text From Source"   , "item", tblText, #tblItem, 180, "align_left")
		fhOutputResultSetColumn("Note"               , "item", tblNote, #tblItem, 180, "align_left")
	else
		fhMessageBox("\n No matching Source Citations found. \n")
	end
end -- function Main()

Main()

Source:Replace-Selected-Source-Citations-1.fh_lua