Create Source Template Definitions.fh_lua

--[[
@Title:       Create Source Template Definitions
@Type:        Standard
@Author:      Mark Draper
@Version:     1.0
@LastUpdated: 20 Oct 2021
@Licence:     This plugin is copyright (c) 2021 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: Creates a Template Definition for user-defined Source Templates where this does not exist
              already, and writes it to the Family Historian Custom Source Templates folder.  This enables
              the definitions to be exported for use in other Projects.
]]

fhInitialise(7,0,0, 'save_recommended')

require('lfs')
require('iuplua')
iup.SetGlobal('CUSTOMQUITMESSAGE', 'YES')

function main()

	local pT = fhNewItemPtr()
	pT:MoveToFirstRecord('_SRCT')

	-- check if project is using templates

	if pT:IsNull() then
		fhMessageBox('This project does not use Templated Sources!', 'MB_OK', 'MB_ICONSTOP')
		return
	end

	-- check for duplicate names

	if CheckDuplicates() then return end

	-- read in available template definitions

	local tblCollections, tblHeaders = Initialize()

	-- check Project templates for missing definitions and links

	local tblMissing = {}
	local tblX = {}
	while pT:IsNotNull() do
		if not CheckTemplate(pT, tblCollections) then
			table.insert(tblMissing, pT:Clone())
		end
		if fhCallBuiltInFunction('LinksTo', pT) == 0 then table.insert(tblX, pT:Clone()) end
		pT:MoveNext('SAME_TAG')
	end

	if #tblMissing == 0 then
		local Msg = 'All Templated Sources have a corresponding definition.\n\nSelect "Tools>Source ' ..
				'Template Definitions from the main menu to check that they are fully synchronized.'
		fhMessageBox(Msg, 'MB_OK', 'MB_ICONINFORMATION')
		return
	end

	local Msg = '\nThere are '  .. #tblMissing .. ' Source Templates in this Project without a ' ..
			'corresponding entry in the Source Definitions.\n\n'

	repeat
		local Option = iup.Alarm('Create Source Template Definitions (1.0)', Msg,
				'Create Definitions', 'Help', 'Cancel')
		if Option == 2 then 
			local H = 'https://pluginstore.family-historian.co.uk/help/create-source-template-definitions'
			fhShellExecute(H)		-- keep line length below self-imposed style limit
			fhSleep(1000) 			-- slight pause to suspend immediate redraw
		elseif Option == 3 then return end
	until Option == 1

	-- work through missing template definitions

	local tblUpdated = {}		-- new or updated collections
	for k, Template in ipairs(tblMissing) do
		local pC = fhGetItemPtr(Template, '~.COLL')
		tblUpdated[fhGetValueAsText(pC)] = true
		GenerateTemplate(Template, tblCollections)
	end

	for Collection, _ in pairs(tblUpdated) do
		UpdateDefinitionFile(Collection, tblCollections, tblHeaders)
	end

	local Msg = 'Template update complete.\n\nSelect "Tools>Source Template Definitions..." from the ' ..
			'main menu to check that they are fully synchronized.'

	if #tblX > 0 then Msg = Msg .. '\n\n' ..#tblX .. ' Source Templates have no linked sources.  ' ..
			'Do you want to delete these from your project? They can be recreated from the Definitions ' ..
			'you have just saved if required later.' end

	if #tblX > 0 then
		if fhMessageBox(Msg, 'MB_YESNO', 'MB_ICONQUESTION') == 'Yes' then
			for _, v in ipairs(tblX) do fhDeleteItem(v) end
			fhUpdateDisplay()
		end
	else
		fhMessageBox(Msg, 'MB_OK', 'MB_ICONINFORMATION')
	end
end

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

function CheckDuplicates()

-- checks for duplicated Template name

	local tblT = {}
	local Duplicates = false

	local pT = fhNewItemPtr()
	pT:MoveToFirstRecord('_SRCT')
	while pT:IsNotNull() do
		local pC = fhGetItemPtr(pT, '~.COLL')
		local pN = fhGetItemPtr(pT, '~.NAME')
		local Name = fhGetValueAsText(pC) .. fhGetValueAsText(pN)

		if tblT[Name] then 
			local Msg = 'There are two Templates called ' .. fhGetValueAsText(pN) .. ' in the ' ..
				fhGetValueAsText(pC) .. ' Collection.  Please ensure that Templates have a unique ' ..
				'name within their Collection prior to creating the Template Definitions.'
			fhMessageBox(Msg, 'MB_OK')
			Duplicates = true
		end

		tblT[Name] = true
		pT:MoveNext('SAME_TAG')
	end

	return Duplicates
end

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

function Initialize()

--[[
Builds up a table of Template Collections.
tblD is a table of Template definitions (Collection name => table of Templates in that Collection).
tblH is a table of header information (Collection name => concatenated table of XML lines prior to
		the Template definitions in the Collection file).
This method enforces unique Template names within a given Collection, although FH permits duplicate names. 
]]

	local field = false
	local Path = fhGetContextInfo('CI_APP_DATA_FOLDER') .. '\\Source Templates\\Custom'
	local TemplateName
	local tblD = {}
	local tblH = {}

	for File in lfs.dir(Path) do
		if File:sub(-5) == '.fhst' then
			local Collection = File:sub(1, -6)		-- remove extension
			local FileContents = fhLoadTextFile(Path .. '\\' .. File)
			if FileContents then
				local tblC = {}
				local tblT = {}
				local header = true
				for line in FileContents:gmatch('[^\r\n]+') do
					if line == '' then
						tblC[TemplateName] = table.concat(tblT, '\n')	-- save completed template
						tblT = {}						-- clear table
					end
				end
				tblD[Collection] = tblC				-- save collection
			end
		end
	end
	return tblD, table.concat(tblH, '\n')
end

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

function CheckTemplate(pT, tblCollections)

	local pC = fhGetItemPtr(pT, '~.COLL')
	local pN = fhGetItemPtr(pT, '~.NAME')

	-- cannot export if no name or collection

	if pC:IsNull() then
		fhMessageBox('Source Template Record ' .. fhGetValueAsText(pN) .. ' does not have a defined ' ..
				'Collection, so cannot be exported!', 'MB_OK', 'MB_ICONEXCLAMATION')
		return true
	end

	if pN:IsNull() then
		fhMessageBox('Unnamed Template definitions cannot be exported', 'MB_OK', 'MB_ICONEXCLAMATION')
		return true
	end

	local Collection = fhGetValueAsText(pC)
	local Name = fhGetValueAsText(pN)

	-- return true if standard collections

	if Collection == 'Essentials' or Collection == 'Advanced' then return true end

	-- return false if template name not listed

	if not tblCollections[Collection] then return false end
	if not (tblCollections[Collection][Name]) then return false end

	return true
end

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

function GenerateTemplate(pT, tblCollections)

	local pF = fhNewItemPtr()
	local Collection = fhGetValueAsText(fhGetItemPtr(pT, '~.COLL'))
	local Name = fhGetValueAsText(fhGetItemPtr(pT, '~.NAME'))

	-- build up new Template definition

	local tblT = {'')

	-- update master collection list

	if not tblCollections[Collection] then
		local tblA = {}
		tblA[Name] = table.concat(tblT, '\n')
		tblCollections[Collection] = tblA
	else
		tblCollections[Collection][Name] = table.concat(tblT, '\n')
	end
end

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

function WriteXMLline(pParent, Tag, XMLtag)

	local p = fhGetItemPtr(pParent, '~.' .. Tag)

	if p:IsNotNull() then
		local Text = fhGetValueAsText(p)
		Text = Text:gsub('&', '&'):gsub('<', '<'):gsub('>', '>')
		local XML = '<' .. XMLtag .. '>' .. Text .. ''
		return XML
	else
		local XML = '<' .. XMLtag .. '/>'
		return XML
	end
end

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

function UpdateDefinitionFile(Collection, tblCollections, tblHeaders)

	local Path = fhGetContextInfo('CI_APP_DATA_FOLDER') .. '\\Source Templates\\Custom'
	local File = Path .. '\\' .. Collection .. '.fhst'
	local FileText

	if tblHeaders[Collection] then
		FileText = tblHeaders[Collection]
	else
		FileText = '\n\n'
	end

	local tblT = {}
	for _, v in pairs(tblCollections[Collection]) do
		table.insert(tblT, v)
	end

	fhSaveTextFile(File, FileText .. table.concat(tblT, '\n') .. '\n\n')
end

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

main()

Source:Create-Source-Template-Definitions-.fh_lua