Find Duplicate Citations.fh_lua

--[[
@Title: Find Duplicate Citations
@Author: Jane Taubman
@Version: 2.1
@LastUpdated: March 2020
@Description: Finds all Facts with more than 1 citation to the same source and optionally deletes second and subsequent citations.

It is recommended that the list option is used first to allow checking of the items to be deleted, before using the delete option. 

2.0 V7 Compatibility
2.1 Support up to 30 duplicate citations on a single fact
]]

require "iuplua"
iup.SetGlobal("CUSTOMQUITMESSAGE","YES")


------------------------------------------------------------------ main

function main()
    tblOutput,iC = createResultTable()
    local intButton = iupButtons("List or Delete All Duplicate Citations","Please Select from the following options","V",
    "List All Duplicate Citations on Facts","Delete All Duplicate Citations on Facts")
    if intButton == 0 then return end
    if intButton == 2 and fhMessageBox("Warning\nPlease confirm deletion of the second and subsequent citations\n where the same source cited more than once for a fact", "MB_YESNO","MB_ICONEXCLAMATION") ~= 'Yes' then 
        return 
    end
    -- Define Columns
    tblOutput.indi = {title='Record',type='item',width=140,align='align_left',content={}}
    tblOutput.fact = {title='Fact',type='item',width=140,align='align_left',content={}}
    for i = 1,30 do
        tblOutput['cite'..i] = {title='Cite '..i,type='item',width=140,align='align_left',content={}}
    end
    for _,sType in ipairs({'FAM','INDI'}) do
        for pi in records(sType) do
            for pfact in facts(pi) do
                -- Add Columns
                local i,j,k = 0,0,0
                local tblDups = {}
                local pfSource = fhNewItemPtr()
                -- Build a list of sources
                for pcite in childitem(pfact,'SOUR') do
                    i = i + 1
                    pfSource = fhGetValueAsLink(pcite)
                    table.insert(tblDups,{rec=pcite:Clone(),id=fhGetRecordId(pfSource)})
                end
                table.sort(tblDups,function(a, b) return a.id > b.id end)
                local bFound = false
                local tblLast = {}
                local tblList = {}

                function outputLine(line)
                    iC = iC + 1
                    tblOutput.indi.content[iC] = pi:Clone()
                    tblOutput.fact.content[iC] = pfact:Clone()
                    if intButton == 1 then
                        for j,tbline in ipairs(tblList) do
                            tblOutput['cite'..j].content[iC] = tbline.rec:Clone()
                        end
                    else
                        for j,tbline in ipairs(tblList) do
                            if j == 1 then
                                tblOutput['cite'..j].content[iC] = tbline.rec:Clone()
                            else
                                if not(fhDeleteItem(tbline.rec)) then error('Error Occured Deleting Citation') end
                            end
                        end
                    end
                end
                for i,tblcite in ipairs(tblDups) do
                    if i > 1 and tblLast.id == tblcite.id then
                        if #tblList == 0 then tblList[1] = tblLast end
                        tblList[#tblList + 1] = tblcite
                    else
                        if #tblList > 0 then
                            -- Output Line
                            outputLine(tblline)
                            tblList = {}
                            
                        end
                    end
                    tblLast = tblcite
                end
                if #tblList > 0 then
                    -- Output Line
                    outputLine(tblline)
                end
            end
        end
    end
    fhOutputResultSetTitles("All Individuals", "All Individuals", "Item List Example Date: %#x")
    for t in tblOutput() do
        fhOutputResultSetColumn(t.title, t.type, t.content, iC, t.width,t.align)
    end
end
------------------------------------------------------------------ custom functions

function childitem(pi)
    local pf = fhNewItemPtr()
    local pf2 = fhNewItemPtr()
    pf:MoveTo(pi,'~.SOUR')
    return function ()
        while pf:IsNotNull() do
            pf2:MoveTo(pf)
            pf:MoveNext('SAME_TAG')
            if pf2:IsNotNull() then return pf2 end
        end
    end
end
------------------------------------------------------------------ standard functions
-- ------------------------------------------------------------ Define table.getn if it does not exist
if not(table.getn) then

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



function facts(pi)
    local pf = fhNewItemPtr()
    local pf2 = fhNewItemPtr()
    pf:MoveToFirstChildItem(pi)
    return function ()
        while pf:IsNotNull() do
            pf2:MoveTo(pf)
            pf:MoveNext()
            if fhIsFact(pf2) then return pf2 end
        end
    end
end

function records(type)
    local pi = fhNewItemPtr()
    local p2 = fhNewItemPtr()
    pi:MoveToFirstRecord(type)
    return function ()
        p2:MoveTo(pi)
        pi:MoveNext()
        if p2:IsNotNull() then return p2 end
    end
end

function createResultTable()
    -- create metatable
    local tblOutput_mt = {}
    tblOutput_mt.col = 0
    tblOutput_mt.seq = {}
    tblOutput_mt.__newindex = function (t,k,v)
        rawset(t,k,v) -- update original table
        local m = getmetatable(t)
        m.col = m.col + 1
        table.insert(m.seq,k)
    end
    tblOutput_mt.__call = function (t)
        local i = 0
        local m = getmetatable(t)
        local n = table.getn(m.seq)
        return function ()
            i = i + 1
            if i <= n then return t[m.seq[i]] end
        end
    end
    local tblOutput = {} -- Define Columns Table
    setmetatable(tblOutput, tblOutput_mt)
    local iC = 0 -- Define count of lines
    return tblOutput,iC
end

function iupButtons(strTitle,strMessage,strBoxType,...)
	local arg = arg or {...}
    local intButton = 0 -- Returned value if X Close button is used
    -- Create the GUI labels and buttons
    local lblMessage = iup.label{title=strMessage,expand="YES"}
    local lblLineSep = iup.label{separator="HORIZONTAL"}
    local iupBox = iup.hbox{homogeneous="YES"}
    if strBoxType == "V" then
        iupBox = iup.vbox{homogeneous="YES"}
    end
    for intArgNum, strButton in ipairs(arg) do
            local btnName = iup.button{title=strButton,expand="YES",padding="4",action=function() intButton=intArgNum return iup.CLOSE end}
            iup.Append(iupBox,btnName)
    end
    -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
    local dialogue = iup.dialog{title=strTitle,iup.vbox{lblMessage,lblLineSep,iupBox},dialogframe="YES",background="250 250 250",gap="8",margin="8x8"}
    dialogue:show()
    if (iup.MainLoopLevel()==0) then iup.MainLoop() end
    dialogue:destroy()
    return intButton
end -- function iupButtons
------------------------------------------------------------------ call main
main()

Source:Find-Duplicate-Citations-4.fh_lua