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
if header then tblH[Collection] = table.concat(tblT, '\n') end -- end of file header
tblT = {}
header = false
field = false
elseif line == '' then
field = true
end
table.insert(tblT, line)
local _, T, _ = line:match('^()(.+)( )$')
if T and not field then
TemplateName = T:gsub('&', '&'):gsub('<', '<'):gsub('>', '>')
end
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 = {''}
table.insert(tblT, WriteXMLline(pT, 'NAME', 'Name'))
table.insert(tblT, WriteXMLline(pT, 'CATG', 'Category'))
table.insert(tblT, WriteXMLline(pT, 'SUBC', 'Subcategory'))
table.insert(tblT, WriteXMLline(pT, 'DESC', 'Description'))
table.insert(tblT, WriteXMLline(pT, 'TITL', 'Record_Title'))
table.insert(tblT, WriteXMLline(pT, 'FOOT', 'Footnote'))
table.insert(tblT, WriteXMLline(pT, 'SHRT', 'Short_Footnote'))
table.insert(tblT, WriteXMLline(pT, 'BIBL', 'Bibliography'))
pF:MoveTo(pT, '~.FDEF')
while pF:IsNotNull() do
table.insert(tblT, '')
table.insert(tblT, WriteXMLline(pF, 'NAME', 'Name'))
table.insert(tblT, WriteXMLline(pF, 'CODE', 'Code'))
table.insert(tblT, WriteXMLline(pF, 'TYPE', 'Type'))
table.insert(tblT, WriteXMLline(pF, 'PROM', 'Prompt'))
table.insert(tblT, WriteXMLline(pF, 'DESC', 'Description'))
if fhGetItemPtr(pF, '~.CITN'):IsNotNull() then table.insert(tblT, 'Yes ') end
table.insert(tblT, ' ')
pF:MoveNext('SAME_TAG')
end
table.insert(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 .. '' .. XMLtag .. '>'
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()
--[[
@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
if header then tblH[Collection] = table.concat(tblT, '\n') end -- end of file header
tblT = {}
header = false
field = false
elseif line == '' then
field = true
end
table.insert(tblT, line)
local _, T, _ = line:match('^()(.+)( )$')
if T and not field then
TemplateName = T:gsub('&', '&'):gsub('<', '<'):gsub('>', '>')
end
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 = {''}
table.insert(tblT, WriteXMLline(pT, 'NAME', 'Name'))
table.insert(tblT, WriteXMLline(pT, 'CATG', 'Category'))
table.insert(tblT, WriteXMLline(pT, 'SUBC', 'Subcategory'))
table.insert(tblT, WriteXMLline(pT, 'DESC', 'Description'))
table.insert(tblT, WriteXMLline(pT, 'TITL', 'Record_Title'))
table.insert(tblT, WriteXMLline(pT, 'FOOT', 'Footnote'))
table.insert(tblT, WriteXMLline(pT, 'SHRT', 'Short_Footnote'))
table.insert(tblT, WriteXMLline(pT, 'BIBL', 'Bibliography'))
pF:MoveTo(pT, '~.FDEF')
while pF:IsNotNull() do
table.insert(tblT, '')
table.insert(tblT, WriteXMLline(pF, 'NAME', 'Name'))
table.insert(tblT, WriteXMLline(pF, 'CODE', 'Code'))
table.insert(tblT, WriteXMLline(pF, 'TYPE', 'Type'))
table.insert(tblT, WriteXMLline(pF, 'PROM', 'Prompt'))
table.insert(tblT, WriteXMLline(pF, 'DESC', 'Description'))
if fhGetItemPtr(pF, '~.CITN'):IsNotNull() then table.insert(tblT, 'Yes ') end
table.insert(tblT, ' ')
pF:MoveNext('SAME_TAG')
end
table.insert(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 .. '' .. XMLtag .. '>'
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