CWCG Build from CSV.fh_lua

--[[
@Title: Commonwealth War Graves Commission CSV Import
@Author: Richard Sellens
@Version: 0.4
@LastUpdated: 23/12/2012 
@Description: Import saved search result CSV file, created by CWGC website
]]

--- Include IUP Extensions
require( "iuplua" )
--
----------------------------------------------------------------------------
-- Functions
----------------------------------------------------------------------------
--
--- GetExistingFile
-- @name GetExistingFile
-- @uses IUP
-- @param strTitle Prompt Title
-- @param strFilter Filter template e.g *.CSV;*.TXT
-- @param strFilterInfo  Display Text for the Selection
function GetExistingFile(strTitle,strFilter,strFilterInfo,strDir)
   -- Creates a file dialog and sets its type, title, filter and filter info
   filedlg = iup.filedlg{dialogtype = "OPEN", title = strTitle,
   filter = strFilter, filterinfo = strFilterInfo,
   directory=strDir}
   -- Shows file dialog in the center of the screen
   filedlg:popup (iup.ANYWHERE, iup.ANYWHERE)
   -- Gets file dialog status
   status = filedlg.status
   return status,filedlg.value
end

--- fromCSV
-- @name	        fromCSV
-- @description   Converts a string to a table of fields, spliting according to normal CSV rules
-- @param         string  comma separated string
-- @return        table of fields
function fromCSV (s)
   s = s .. ','        -- ending comma
   local t = {}        -- table to collect fields
   local fieldstart = 1
	iID = iID + 1
	table.insert(t, iID)
   repeat
      -- next field is quoted? (start with `"'?)
      if string.find(s, '^"', fieldstart) then
         local a, c
         local i  = fieldstart
         repeat
            -- find closing quote
            a, i, c = string.find(s, '"("?)', i+1)
         until c ~= '"'    -- quote not followed by quote?
         if not i then
            error('unmatched "')
         end
         local f = string.sub(s, fieldstart+1, i-1)
         table.insert(t, (string.gsub(f, '""', '"')))
         fieldstart = string.find(s, ',', i) + 1
      else                -- unquoted; find next comma
         local nexti = string.find(s, ',', fieldstart)
         table.insert(t, string.sub(s, fieldstart, nexti-1))
         fieldstart = nexti + 1
      end
   until fieldstart > string.len(s)
   return t
end

--- LoadFile
-- @name          loadfile
-- @description   Loads a CSV file into a table of tables
-- @usage         provide valid filename, use ipairs loop to use resulting table
-- @param         filename  filename to load
-- @return        table of tables
function loadfile(filename)
   contents = {}
   bheader = false
   for line in io.lines(filename) do
      
      fields = fromCSV(line)
      data   = {}
      --
      if bheader then
         --  Build Data Table with the Header Descriptions
         for i,field in ipairs(fields) do
            if field ~= '' then
               data[header[i]] = field
            end
         end
         contents[fields[1]] =data
      else
         -- Grab Column Names
         header = fields
         bheader = true
      end
   end
   return contents
end

--- CreateSource
-- @name          CreateSource
-- @description   Creates a new Source Record SOUR
-- @param         name string 
-- @return        item pointer for new record
function CreateSource(sName)
   local ptrSOUR = fhCreateItem("SOUR")            -- create an Source record and ret ptr to it
   local ptrTITL = fhCreateItem("TITL", ptrSOUR)   -- create a TITLE field within this record, and ret ptr to it
   fhSetValueAsText(ptrTITL, sName)                -- set value of the name using passed in parameter
	local ptrREPO = fhCreateItem("REPO", ptrSOUR)
	fhSetValueAsLink(ptrREPO, CreateRepo())

   return ptrSOUR                                  -- return pointer to the newly-created Individual record
end

--- CreateRepo
-- @name          CreateRepo
-- @description   Creates a new Record REPO
-- @param         name string 
-- @return        item pointer for new record
function CreateRepo()
   local ptrREPO = fhCreateItem("REPO")            
   local ptrTITL = fhCreateItem("NAME", ptrREPO)   
   fhSetValueAsText(ptrTITL, 'Commonwealth War Graves Commission')             
   local ptrADDR = fhCreateItem("ADDR", ptrREPO)   
   fhSetValueAsText(ptrADDR, '2 Marlow Road\nMaidenhead\nBerkshire\nSL6 7DX\nEngland')                
   local ptrPHON = fhCreateItem("PHON", ptrREPO)   
   fhSetValueAsText(ptrPHON, '+44 (0) 1628 634221')                 
   local ptrWEB = fhCreateItem("_WEB", ptrREPO)   
   fhSetValueAsText(ptrWEB, 'http://www.cwgc.org/')          

   return ptrREPO                                  -- return pointer to the newly-created record
end

--- CreateInd
-- @name          CreateInd
-- @description   Creates a new individual Record INDI
-- @param         name string in format  forenames /surname/ NAME
-- @param         sex string either M or F or Male or Female SEX
-- @param         customid string for records custom ID REFN
-- @return        item pointer for new record
function CreateInd(sName, sSex, sCustomID, ptrSource)
   local ptrInd = fhCreateItem("INDI")             -- create an Individual record and ret ptr to it
   local ptrSOUR = fhCreateItem('SOUR', ptrInd)  -- create a source subfield
   fhSetValueAsLink(ptrSOUR, ptrSource)
   
   local ptrName = fhCreateItem("NAME", ptrInd)    -- create a NAME field within this record, and ret ptr to it
   fhSetValueAsText(ptrName, sName)                -- set value of the name using passed in parameter
   
   if sSex ~= nil and sSex ~= ' ' then
      local ptrSex = fhCreateItem("SEX", ptrInd)   -- create a SEX field within this record, and ret ptr to it
      fhSetValueAsText(ptrSex, sSex)               -- set value of sex using passed in parameter
   end

   return ptrInd                                   -- return pointer to the newly-created Individual record
end

--- AddEvent
-- @name          AddEvent
-- @description   Adds an event to a record
-- @usage         event must be a valid data reference for the pointer provided
-- @param         ptrRecord point for individual record INDI
-- @param         event event type eg BIRT, DEAT etc
-- @param         eventdate string containing date in FH reconised format DATE
-- @param         eventplace  place for event PLAC
-- @return        none
function AddEvent(ptrRecord,event,eventdate,eventplace,ptrSource)
	ptrEvent = fhCreateItem(event,ptrRecord)   -- create a event field
	ptrSOUR = fhCreateItem('SOUR', ptrEvent)   -- create a source subfield for it
   fhSetValueAsLink(ptrSOUR, ptrSource)

   if eventdate ~= nil then
      -- create a date subfield for it & set value of the date using passed in parameter
		ptrDate = fhCreateItem('DATE', ptrEvent)   
		fhSetValueAsDate(ptrDate, eventdate)
   end
   if eventplace ~= nil then
      -- create a place subfield for it & set value of the place using passed in parameter
      ptrPlace = fhCreateItem('PLAC', ptrEvent) 
      fhSetValueAsText(ptrPlace, eventplace)
   end
   
   return(ptrEvent)
end

-- Set first letter to capital and the rest to lower case.
function tchelper(first, rest)
    return first:upper()..rest:lower()
end

-- Set change the "middle" letter to upper case (eg McMull).
function upperspecial (start,middle,rest)
    return start..middle:upper()..rest:lower()
end

-- Set change the first letter(s) to lower case (eg de'Watt).
function lowerspecial (start,middle,rest)
    return start:lower()..middle:upper()..rest:lower()
end
function lowerspecial2 (start,middle,rest)
    return start..middle..rest:lower()
end
function alllower (start)
    return start:lower()
end
-- captialise string
function captialise(str)
    str = str:gsub("(%a)([%w_]*)", tchelper)
    -- Add Special Cases Here
    str = str:gsub("(Mac)(%a)([%w_]*)", upperspecial)
    str = str:gsub("(Mc)(%a)([%w_]*)", upperspecial)
    str = str:gsub("(De')(%a)([%w_]*)", lowerspecial)
    str = str:gsub("(O')(%a)([%w_]*)", upperspecial)
    str = str:gsub("( Of )", alllower)
    str = str:gsub("(%a)([%w_]*)('S)", lowerspecial2)
    str = str:gsub("(%d+)([%w_]*)", tchelper)
    return(str)
end

function MilService(str)
	
	if string.find(str, 'Royal Navy')~= nil then
		tService = 'Royal Navy'
	elseif string.find(str, 'Royal Naval Volunteer Reserve')~= nil  then
		tService = 'Royal Naval Volunteer Reserve'
	elseif string.find(str, 'Royal Naval Reserve')~= nil  then
		tService = 'Royal Naval Reserve'
	elseif string.find(str, 'Royal Air Force')~= nil  then
		tService = 'Royal Air Force'
	elseif string.find(str, 'Canadian')~= nil  then
		tService = 'Canadian Over-Seas Expeditionary Force'
	elseif string.find(str, 'Australian')~= nil  then
		tService = 'Australian Infantry'
	elseif string.find(str, 'Mercantile Marine')~= nil  then
		tService = 'Merchant Navy'
	else
		tService = 'British Army'
	end

	return(tService)
end 

function MilAward(str)
   local tandBar = ''
      
   if string.find(str, 'and Bar')~= nil then
      tandBar = string.sub(str, string.find(str, 'and Bar') - 1)   
      str = string.sub(str, 1, string.find(str, 'and Bar') - 2)  
   end
	
	if str == 'M M' then
		tMilAward = 'Military Medal'
	elseif str == 'M B E'  then
		tMilAward = 'MBE'  
	elseif str == 'M C'  then
		tMilAward = 'Military Cross'    
	elseif str == 'D C M'  then
		tMilAward = 'Distinguished Conduct Medal'    
	elseif str == 'V C'  then
		tMilAward = 'Victoria Cross'    
	elseif str == 'T D'  then
		tMilAward = 'Territorial Decoration'     
	elseif str == 'M S M'  then
		tMilAward = 'Meritorious Service Medal'      
	elseif str == 'D S O'  then
		tMilAward = 'Distinguished Service Order'
	else
		tMilAward = captialise(str)
	end	
                       
	return(tMilAward ..tandBar)
end 

function Gender(str)
	if str == nil then
		tGender = ' '
	elseif string.find(str, 'MR. ')~= nil then
		tGender = 'M'
	elseif string.find(str, 'MR ')~= nil then
		tGender = 'M'
	elseif string.find(str, 'MRS. ')~= nil then
		tGender = 'F'
	elseif string.find(str, 'MISS ')~= nil then
		tGender = 'F'
	else
		tGender = ' '
	end	

	return(tGender)
end 

function ExtractAddlInfo (s)
   AddlNote = ''
   
   tab_Individual = {}
   tab_Family = {}
   local tab_Siblings = {}
   local tab_Children = {}
   
   -- Initialise Number of relatives fields
   tab_Family.NumSiblings = 0
   tab_Family.NumChildren = 0

	if s ~= nil then
      s = s .. ';'        -- ending 
      local t2 = {}        -- table to collect fields
      local fieldstart = 1
      repeat
         -- next field is quoted? (start with `"'?)
         if string.find(s, '^"', fieldstart) then
            local a, c
            local i  = fieldstart
            repeat
               -- find closing quote
               a, i, c = string.find(s, '"("?)', i+1)
            until c ~= '"'    -- quote not followed by quote?
            if not i then
               error('unmatched "')
            end
            local f = string.sub(s, fieldstart+1, i-1)
            tInsertTxt = string.gsub(f, '""', '"')
            fieldstart = string.find(s, ';', i) + 1
         else                -- unquoted; find next semi-colon
            local nexti = string.find(s, ';', fieldstart)
            tInsertTxt = string.sub(s, fieldstart, nexti-1)
            fieldstart = nexti + 1
         end

			-- Extract Details        
                 
			if string.find(tInsertTxt, 'ADOPTED')~= nil then
            tab_Family.Adopted = true
			end    
			if string.find(tInsertTxt, 'FOSTER')~= nil then
            tab_Family.Foster = true
			end

         if string.find(tInsertTxt, 'BORN AT')~= nil then
            tab_Individual.BirthPlace = captialise(string.sub(tInsertTxt, string.find(tInsertTxt, 'BORN AT')+8))   
         elseif string.find(tInsertTxt, 'DIED AT')~= nil then
			   tab_Individual.DeathPlace = captialise(string.sub(tInsertTxt, string.find(tInsertTxt, 'DIED AT')+8))
         elseif string.find(tInsertTxt, 'ENLISTED')~= nil then
            tab_Individual.Enlistment = string.sub(tInsertTxt, string.find(tInsertTxt, 'ENLISTED')+9)
         elseif string.find(tInsertTxt, 'EDUCATED AT') ~= nil then
            tab_Individual.Educated = captialise(string.sub(tInsertTxt, string.find(tInsertTxt, 'EDUCATED AT')+12)) 
         elseif string.find(tInsertTxt, 'SERVED IN') ~= nil then
            tab_Individual.MilService = captialise(string.sub(tInsertTxt, string.find(tInsertTxt, 'SERVED IN')+10))
         elseif string.find(tInsertTxt, 'NATIVE OF')~= nil then
            tab_Individual.Native = captialise(string.sub(tInsertTxt, string.find(tInsertTxt, 'NATIVE OF')+10))
         elseif string.find(tInsertTxt, 'HUSBAND OF')~= nil then
            tab_Individual.Gender = 'M'
            tab_Family.Spouse = true
            tab_Family.SpouseGender = 'F'
            local tBSpouse = string.sub(tInsertTxt, string.find(tInsertTxt, 'HUSBAND OF')+11)
            if string.find(tBSpouse, ', OF')~= nil then
               tab_Family.SpousePlc = captialise(string.sub(tBSpouse, string.find(tBSpouse, ', OF')+5))
               tBSpouse = string.sub(tBSpouse, 1, string.find(tBSpouse, ', OF')-1)
            end
            tBSpouse = tBSpouse:gsub(tMainSurname, '')
            tab_Family.SpouseFullName = FormatName(tBSpouse)
			elseif string.find(tInsertTxt, 'BROTHER OF')~= nil then
            tab_Individual.Gender = 'M'
            tab_Family.Child = true
            tab_Family.NumSiblings = tab_Family.NumSiblings + 1
            local tab_SiblingDetails = {}
            local tSibling = string.sub(tInsertTxt, string.find(tInsertTxt, 'BROTHER OF')+10)
            if string.find(tSibling, ', OF')~= nil then
               tab_SiblingDetails.ResidencePlc = captialise(string.sub(tSibling, string.find(tSibling, ', OF')+5))
               tSibling = string.sub(tSibling, 1, string.find(tSibling, ', OF')-1)
            end
            tab_SiblingDetails.Gender = Gender(tSibling)
            tab_SiblingDetails.FullName = FormatName(tSibling)
            table.insert(tab_Siblings, tab_SiblingDetails)
         elseif string.find(tInsertTxt, 'HIS BROTHER ') ~= nil or string.find(tInsertTxt, 'HIS BROTHER, ') ~= nil or 
            string.find(tInsertTxt, 'HIS BROTHERS ') ~= nil then
            tab_Family.Child = true
            local strtpos = 1
            if string.find(tInsertTxt, 'HIS BROTHER ') ~= nil then
               strtpos = string.find(tInsertTxt, 'HIS BROTHER ') + 12
            elseif string.find(tInsertTxt, 'HIS BROTHER, ') ~= nil then
               strtpos = string.find(tInsertTxt, 'HIS BROTHER, ') + 13 
            elseif string.find(tInsertTxt, 'HIS BROTHERS ')  ~= nil then
               strtpos = string.find(tInsertTxt, 'HIS BROTHERS ') + 13
            end 
            local endpos = 1
            if string.find(tInsertTxt, 'ALSO FELL') ~= nil then
               endpos = string.find(tInsertTxt, 'ALSO FELL') - 1 
            elseif string.find(tInsertTxt, 'ALSO DIED')  ~= nil then
               endpos = string.find(tInsertTxt, 'ALSO DIED') - 1
            end
            local NameList = string.sub(tInsertTxt, strtpos, endpos) ..' AND '
                                    
            local fieldstart = 1
            repeat
               local nexti = string.find(NameList, ' AND ', fieldstart)
               tab_Family.NumSiblings = tab_Family.NumSiblings + 1
               local tab_SiblingDetails = {}
               tab_SiblingDetails.Gender = 'M'
               tab_SiblingDetails.FullName = FormatName(string.sub(NameList, fieldstart, nexti-1))
               table.insert(tab_Siblings, tab_SiblingDetails) 
               fieldstart = nexti + 5
            until fieldstart > string.len(NameList)
         elseif string.find(tInsertTxt, 'HIS BROTHER-IN-LAW', 1, true) ~= nil then
            tab_Family.Child = true
            tab_Family.NumSiblings = tab_Family.NumSiblings + 1
            local tab_SiblingDetails = {}
            tab_SiblingDetails.Gender = 'F'
            tab_SiblingDetails.FullName = '/' ..captialise(tMainSurname) ..'/'
            tab_SiblingDetails.SpouseGender = 'M'
            local strtpos = string.find(tInsertTxt, 'HIS BROTHER-IN-LAW', 1, true) + 19 
            local endpos = -1
            if string.find(tInsertTxt, 'ALSO FELL') ~= nil then
               endpos = string.find(tInsertTxt, 'ALSO FELL') - 1 
            elseif string.find(tInsertTxt, 'ALSO DIED')  ~= nil then
               endpos = string.find(tInsertTxt, 'ALSO DIED') - 1
            end
            local NameList = string.sub(tInsertTxt, strtpos, endpos)
            tab_SiblingDetails.SpouseName = FormatName(NameList)                                       
            table.insert(tab_Siblings, tab_SiblingDetails)
         elseif string.find(tInsertTxt, 'FATHER OF')~= nil then
            tab_Individual.Gender = 'M'
            tab_Family.NumChildren = tab_Family.NumChildren + 1
            local tab_ChildDetails = {}
            local tChild = string.sub(tInsertTxt, string.find(tInsertTxt, 'FATHER OF')+10)
            if string.find(tChild, ', OF')~= nil then
               tab_ChildDetails.ResidencePlc = captialise(string.sub(tChild, string.find(tChild, ', OF')+5))
               tChild = string.sub(tChild, 1, string.find(tChild, ', OF')-1)
            end
            tab_ChildDetails.FullName = FormatName(tChild)
            table.insert(tab_Children, tab_ChildDetails)
         elseif string.find(tInsertTxt, 'NEPHEW OF')~= nil then
            tab_Individual.Gender = 'M'
            table.insert(t2, tInsertTxt)   
         elseif string.find(tInsertTxt, 'GRANDSON OF')~= nil then
            local tGrandParent1 = nil
            local tGrandParent2 = nil
            local tGrandParents = nil
            tab_Individual.Gender = 'M'
            tab_Family.GrandChild = true
            tGrandParents = string.sub(tInsertTxt, string.find(tInsertTxt, 'GRANDSON OF')+12)
            if string.find(tGrandParents, tMainSurname) ~= nil then
               tab_Family.GrandChildFather = true
            else
               tab_Family.GrandChildMother = true   
            end
            if string.find(tGrandParents, ', OF') ~= nil then
               tab_Family.GrandParentsPlc = captialise(string.sub(tGrandParents, string.find(tGrandParents, ', OF')+5))
               tGrandParents = string.sub(tGrandParents, 1, string.find(tGrandParents, ', OF')-1)
            end
            if string.find(tGrandParents, 'MR. AND MRS.') ~= nil then
               tGrandParent1 = string.sub(tGrandParents, string.find(tGrandParents, 'MR. AND MRS.')+13)
               tab_Family.GrandParent1Gender = 'M'
               tab_Family.GrandParent2Gender = 'F'
            else
               if string.find(tGrandParents, ' AND ') ~= nil then
                  tGrandParent1 = string.sub(tGrandParents, 1, string.find(tGrandParents, ' AND ')-1)
                  if string.find(tGrandParent1, tMainSurname) == nil then
                     tGrandParent1 = tGrandParent1 ..' ' ..tMainSurname     
                  end
                  tGrandParent2 = string.sub(tGrandParents, string.find(tGrandParents, ' AND ')+5)
                  tab_Family.GrandParent1Gender = Gender(tGrandParent1)
                  tab_Family.GrandParent2Gender = Gender(tGrandParent2)
               else
                  tGrandParent1 = tGrandParents
                  tab_Family.GrandParent1Gender = Gender(tGrandParent1)
               end
            end 
            if tab_Family.GrandParent1Gender == 'F' then
               tGrandParent1 = tGrandParent1:gsub(tMainSurname, '')   
            end
            tab_Family.GrandParent1 = FormatName(tGrandParent1)
            if tGrandParent2 ~= nil then
               if tab_Family.GrandParent2Gender ~= 'M' then
                  tGrandParent2 = tGrandParent2:gsub(tMainSurname, '')   
               end 
               tab_Family.GrandParent2 = FormatName(tGrandParent2)
            end
         elseif string.find(tInsertTxt, 'SON OF') ~= nil or string.find(tInsertTxt, 'CHILD OF') ~= nil then
            local tParent1 = nil
            local tParent2 = nil
            local tParents = nil
            tab_Individual.Gender = 'M'
            tab_Family.Child = true
            if string.find(tInsertTxt, 'SON OF') ~= nil then
               tParents = string.sub(tInsertTxt, string.find(tInsertTxt, 'SON OF')+7)
            elseif string.find(tInsertTxt, 'CHILD OF') ~= nil then
               tParents = string.sub(tInsertTxt, string.find(tInsertTxt, 'CHILD OF')+9)   
            end 
            
            if string.find(tParents, ', OF') ~= nil then
               tab_Family.ParentsPlc = captialise(string.sub(tParents, string.find(tParents, ', OF')+5))
               tParents = string.sub(tParents, 1, string.find(tParents, ', OF')-1)
            end
            if string.find(tParents, 'MR. AND MRS.') ~= nil then
               tParent1 = string.sub(tParents, string.find(tParents, 'MR. AND MRS.')+13)
               tab_Family.Parent1Gender = 'M'
               tab_Family.Parent2Gender = 'F'
            else
               if string.find(tParents, ' AND ') ~= nil then
                  tParent1 = string.sub(tParents, 1, string.find(tParents, ' AND ')-1)
                  if string.find(tParent1, tMainSurname) == nil then
                     tParent1 = tParent1 ..' ' ..tMainSurname     
                  end
                  tParent2 = string.sub(tParents, string.find(tParents, ' AND ')+5)
                  tab_Family.Parent1Gender = Gender(tParent1)
                  tab_Family.Parent2Gender = Gender(tParent2)
               else
                  tParent1 = tParents
                  tab_Family.Parent1Gender = Gender(tParent1)
               end
            end
            if tab_Family.Parent1Gender == 'F' then
               tParent1 = tParent1:gsub(tMainSurname, '')   
            end
            tab_Family.Parent1 = FormatName(tParent1)
            if tParent2 ~= nil then
               if tab_Family.Parent2Gender ~= 'M' then
                  tParent2 = tParent2:gsub(tMainSurname, '')   
               end 
               tab_Family.Parent2 = FormatName(tParent2)
            end
         else
            table.insert(t2, tInsertTxt)
			end
      until fieldstart > string.len(s)

		for i,str in ipairs(t2) do
			AddlNote = AddlNote ..str ..'\n'
		end
	end
   
   if tab_Family.NumSiblings > 0 then
      tab_Family.Siblings = tab_Siblings
   end
   
   if tab_Family.NumChildren > 0 then
      tab_Family.Children = tab_Children
   end
   return(AddlNote)
end

--- AddFamilyMember
-- @name          AddFamilyMember
-- @description   Adds a person to a family
-- @param         type Type is either  SPOU or CHIL  for Spouse or Child.
-- @param         family record pointer
-- @param         individual record pointer)
-- @return        none
function AddFamilyMember(type,ptrfam,ptrind)
   ptrLink = fhCreateItem(type, ptrfam )
   fhSetValueAsLink(ptrLink, ptrind)
end

--- CleanPlace
-- @name          CleanPlace
-- @description   Performs various clean up tasks on a place value
-- @param         input place field
-- @return        updated place field
function CleanPlace(strPlace)
   local strRetPlace = nil 
   
   if strPlace ~= nil then
      strRetPlace = strPlace
      -- swap comma inside quoted field to outside
      strRetPlace= strRetPlace:gsub("''", '"')
      strRetPlace= strRetPlace:gsub(',"', '",')

      -- remove comma between 'house number' and street
      if string.find(strRetPlace, ', ')~= nil then
         local str = string.sub(strRetPlace, 1, string.find(strRetPlace, ', ')-1)
         local str2 = string.sub(strRetPlace, string.find(strRetPlace, ', ')+1)
         if tonumber(str) ~= nil then   --it's a number
            strRetPlace = str:gsub(' ', '') ..str2
         else
            if string.find(str2, ', ') ~= nil then
               local str3 = string.sub(str2, string.find(str2, ', ')+1)
               str2 = string.sub(str2, 1, string.find(str2, ', ')-1)
               if tonumber(str2) ~= nil then   --it's a number
                  strRetPlace = str ..', ' ..str2:gsub(' ', '') ..str3 
               end
            end
         end
      end
   
      -- remove trailing 'full stop'
      if string.sub(strRetPlace, -1) == '.' then
         strRetPlace = string.sub(strRetPlace, 1, string.len(strRetPlace)-1)
      end
   
      -- correct "'S" in place
      strRetPlace= strRetPlace:gsub("'S ", "'s ")   
   end

   return(strRetPlace)
end

--- FormatName
-- @name          FormatName
-- @description   Performs various clean up tasks on a name value
-- @param         input name field
-- @return        updated name field
function FormatName(inName)
   local strRetName = nil
   local forename = ''
   local surname = tMainSurname  
   local suffix = ''
 
   -- remarried ? 
   if string.find(inName, '%(FORMERLY') ~= nil then
      inName = string.sub(inName, 1, string.find(inName, '%(FORMERLY') -2)
      local fieldstart = 1
      repeat
         if string.find(inName, ' ', fieldstart, true) ~= nil then
            local findpos = string.find(inName, ' ', fieldstart, true)
            forename = string.sub(inName, 1, findpos - 1) 
            surname = ''
            fieldstart = findpos + 1 
         else
            fieldstart = string.len(inName) + 1  
         end
      until fieldstart > string.len(inName)
      
   -- Maiden name ?
	elseif string.find(inName, '%(NEE') ~= nil then
      forename = string.sub(inName, 1, string.find(inName, '%(NEE') -2) 
      if string.find(forename, tMainSurname)~= nil then
         forename = string.sub(forename, 1, string.find(forename, tMainSurname)-2)
      end
      surname = string.sub(inName, string.find(inName, '%(NEE') +5)
      surname = surname:gsub('%)', '')
	else
      -- Extract forname, surname & suffix fields
      if string.find(inName, tMainSurname)~= nil then
         forename = string.sub(inName, 1, string.find(inName, tMainSurname)-2)
         local strtPos = string.find(inName, tMainSurname) + string.len(tMainSurname) + 1
         suffix = string.sub(inName, strtPos, string.len(inName)-2)
      else
         forename = inName
         surname = ''
      end
   end  
   
   -- Drop 'titles'
   forename= forename:gsub('MR. ', '')
   forename= forename:gsub('MR ', '')
   forename= forename:gsub('MRS. ', '') 
   forename= forename:gsub('MISS ', '') 
   forename= forename:gsub('THE LATE ', '')

   strRetName = captialise(forename) ..' /' ..captialise(surname) ..'/' ..captialise(suffix)

   return(strRetName)
end

--- ExtractEnlistmentDetails
-- @name          ExtractEnlistmentDetails
-- @description   takes the enlistment string & extracts details
-- @param         input Enlistment text string field
-- @return        none
function ExtractEnlistmentDetails(strEnlist)
   local EnlistmentNote = ''
   -- Correct 'Regiment' text
   strEnlist = strEnlist:gsub("REGT.,", "Regt. ,") 

   -- Remove any unwanted text in field
   strEnlist = strEnlist:gsub("%.,", "")
   strEnlist = strEnlist:gsub("IN ", "")
 
   -- Extract 'data' blocks from Enlistment String
   local t4 = SplitString_Comma(strEnlist .. ',')

   -- Process Blocks
   for i,str in ipairs(t4) do  
      -- Look for Enlistment Date
      local enlistdatefield = fhNewDate()
      if enlistdatefield:SetValueAsText(str,false) then
         tab_Individual.EnlistmentDate =  enlistdatefield
      else
         EnlistmentNote = EnlistmentNote ..str ..'\n'
      end
   end
   
   if EnlistmentNote  ~= '' then
      tab_Individual.EnlistmentNote = captialise(EnlistmentNote) 
   end
end


--- SplitString_Comma
-- @name          SplitString_Comma
-- @description   splits string into blocks based on comma
-- @param         input  text string field
-- @return        table of blocks
function SplitString_Comma(inStr)
   -- Extract 'data' blocks from String
   local RetTable = {}        -- table to collect fields
   local fieldstart = 1
   
   inStr = inStr:gsub(", ", ",")
   repeat
      -- next field is quoted? (start with `"'?)
      if string.find(inStr, '^"', fieldstart) then
         local a, c
         local i  = fieldstart
         repeat
            -- find closing quote
            a, i, c = string.find(inStr, '"("?)', i+1)
         until c ~= '"'    -- quote not followed by quote?
         if not i then
            error('unmatched "')
         end
         local f = string.sub(inStr, fieldstart+1, i-1)
         tInsertTxt = string.gsub(f, '""', '"')
         fieldstart = string.find(inStr, ',', i) + 1
      else                -- unquoted; find next semi-colon
         local nexti = string.find(inStr, ',', fieldstart)
         tInsertTxt = string.sub(inStr, fieldstart, nexti-1)
         fieldstart = nexti + 1
      end

      table.insert(RetTable, tInsertTxt)
   until fieldstart > string.len(inStr) 

   return(RetTable) 
end

--
----------------------------------------------------------------------------
-- Main Code
----------------------------------------------------------------------------
--
-- Check for Empty gedcom
--
ptrind = fhNewItemPtr()
ptrind:MoveToFirstRecord("INDI")    -- set the first to point to the first Source record
if ptrind:IsNotNull() then
   sMessage = [[
   Warning: Data exists in the GEDCOM file.
   This script takes no account of existing data
   and is better used on a New empty file.
   Do you wish to continue?
   ]]
   a = fhMessageBox(sMessage, "MB_YESNO","MB_ICONEXCLAMATION")
   if a == "No" then
      error('Script Aborted')
   end
end
--
-- Prompt for file to Process
--
status,strfilename = GetExistingFile("Select Missing File","*.csv","Comma Separated File","Documents")
if status == '-1' then
   error('Prompt Cancelled')
end
iID = 0
-- Call loadfile function to return a table with the data in.
contents = loadfile(strfilename)
family = {}
individual = {}

-- Create Source record
ptrSource = CreateSource('CWGC Search')

-- Create People
for key,data in pairs(contents) do
   -- Create Name
	if data['forename'] ~= nil then
		fornames = data['forename']
	else
		fornames = data['initials']
	end
   name = captialise(fornames)..' /'..captialise(data['surname'])..'/'
   tMainSurname = data['surname'] 
   -- Create Individual Record
	AddlInfo = ExtractAddlInfo(data['additionalinformation'])
   ptrind =  CreateInd(name, tab_Individual.Gender, 0, ptrSource)
   individual[0] = ptrind:Clone()

	tempdatefield = fhNewDate()
   tempdatefield:SetValueAsText(data['date_of_death'],true)
	dpDatePt = tempdatefield:GetDatePt1()

   -- Add Birth Event
	if data['age_text'] ~= nil then
		iAge = data['age_text']
		iYear = dpDatePt:GetYear()
		iBirth = iYear - iAge
		tempdatefield = fhNewDate()
		tempdatefield:SetValueAsText(iBirth,true)
		eventdatefield = fhNewDate()
		eventdatefield:SetSimpleDate(tempdatefield:GetDatePt1(), 'cal')
      ptrBIRT = AddEvent(ptrind, 'BIRT', eventdatefield, CleanPlace(tab_Individual.BirthPlace), ptrSource)
   else
      if tab_Individual.BirthPlace ~= nil then
         ptrBIRT = AddEvent(ptrind, 'BIRT', nil, CleanPlace(tab_Individual.BirthPlace), ptrSource)
      end
	end
   
	-- Add Death Event
	eventdatefield = fhNewDate()
	eventdatefield:SetValueAsText(data['date_of_death'],true)   
   ptrDEAT = AddEvent(ptrind, 'DEAT', eventdatefield, CleanPlace(tab_Individual.DeathPlace), ptrSource)

	if data['age_text'] ~= nil then
		ptrAge	= fhCreateItem('AGE', ptrDEAT)
		tAge = data['age_text']..'y'
		fhSetValueAsText(ptrAge, tAge)
	end
   
	-- Add Burial/Memorial Event
	if string.find(data['cemeterymemorial'], 'MEMORIAL') ~= nil  then
		strTag = fhGetFactTag("Memorial", "Event", "INDI", false)
		tRefTxt = 'Memorial Reference : '
	else
		strTag = 'BURI'
		tRefTxt = 'Burial Reference : '
	end	  
   if data['country'] == 'Civilian War Dead' then
		tPLAC = ', , United Kingdom'
	else
		tPLAC = ', , '..captialise(data['country'])
	end	  
   ptrBURMEM = AddEvent(ptrind, strTag, nil, tPLAC, ptrSource) 

	ptrPlace = fhCreateItem('ADDR', ptrBURMEM)	
	tADDR = captialise(data['cemeterymemorial'])..tPLAC
	fhSetValueAsText(ptrPlace, tADDR)   
	if data['gravereference'] ~= nil then
		ptrNOTE = fhCreateItem('NOTE2', ptrBURMEM) 				
		fhSetValueAsText(ptrNOTE, tRefTxt..data['gravereference'])
	end

	if data['rank'] ~= 'Civilian' then
		-- Add Military Enlistment
      if tab_Individual.Enlistment ~= nil then
         local dummy = ExtractEnlistmentDetails(tab_Individual.Enlistment)
      end   
		strTag = fhGetFactTag("Military Enlistment", "Attribute", "INDI", false)
      if tab_Individual.EnlistmentDate ~= nil then
         dToDate = tab_Individual.EnlistmentDate
      else
         dToDate = fhNewDate()
         dToDate:SetRange('before',dpDatePt)
      end
      ptrEnlist = AddEvent(ptrind, strTag, dToDate, nil, ptrSource)    			
		fhSetValueAsText(ptrEnlist, MilService(data['regiment'])) 
		ptrNOTE = fhCreateItem('NOTE2', ptrEnlist) 
		if data['servicenumberExport'] ~= nil then
			tSrvNum = 'Military Service No. : '..string.gsub(data['servicenumberExport'], '\'', '')
		else
			tSrvNum = 'Military Service No. : '
		end       
		fhSetValueAsText(ptrNOTE, tSrvNum)
      if tab_Individual.EnlistmentNote ~= nil then
         local ptrNOTE2 = fhCreateItem('NOTE2', ptrEnlist)
         fhSetValueAsText(ptrNOTE2, tab_Individual.EnlistmentNote)
      end

		-- Add Military Service
		strTag = fhGetFactTag("Military Service", "Attribute", "INDI", false)              
		dToDate = fhNewDate()
		dToDate:SetPeriod('to',dpDatePt)
      ptrMilS = AddEvent(ptrind, strTag, dToDate, nil, ptrSource)
		if data['unitshipsquadron'] ~= nil then
			tUnit = data['unitshipsquadron']..' '..data['regiment']
		else
			tUnit = data['regiment']
		end
   		tMilSvc = data['rank']..', '..tUnit 			
		fhSetValueAsText(ptrMilS, tMilSvc)

		-- Add Military Award
		if data['honours_awards'] ~= nil then
         local t3 = SplitString_Comma(data['honours_awards'] .. ',')
         local strTag = fhGetFactTag("Military Award", "Attribute", "INDI", false) 
         for i,str in ipairs(t3) do  
			   ptrMilA = AddEvent(ptrind, strTag, nil, nil, ptrSource)	
			   fhSetValueAsText(ptrMilA, MilAward(str))
		   end
		end 
      
		-- Add Military Conflict
		if tab_Individual.MilService ~= nil then
         local strTag = fhGetFactTag("Military Conflict", "Attribute", "INDI", false)  
         local ptrMilS = AddEvent(ptrind, strTag, nil, nil, ptrSource)	
         fhSetValueAsText(ptrMilS, tab_Individual.MilService)
		end
	end
   
   -- Add Education Detail
   if tab_Individual.Educated ~= nil then
      ptrEduc = AddEvent(ptrind, 'EDUC', nil, nil, ptrSource) 			
		fhSetValueAsText(ptrEduc, tab_Individual.Educated)
   end
   
   -- Add 'Native of' details
   if tab_Individual.Native ~= nil then
      ptrResi = AddEvent(ptrind, 'RESI', nil, CleanPlace(tab_Individual.Native), ptrSource)
   end

	-- Add Note
	if AddlInfo ~= nil and AddlInfo ~= '' then
		ptrNOTE = fhCreateItem('NOTE2', ptrind) 
		fhSetValueAsText(ptrNOTE, AddlInfo)
	end
	
	-- Add Spouse & Family
   if tab_Family.Spouse ~= nil or tab_Family.NumChildren > 0 then
      local dAfterDate = fhNewDate()
      dAfterDate:SetRange('after',dpDatePt) 
      
      -- Create New Family
      ptrSpouseFam = fhCreateItem("FAM")
      
      -- Add main individual to family
      if tab_Individual.Gender == 'F' then
			tRel = 'WIFE'
			tSpouseRel = 'HUSB'
      else
			tRel = 'HUSB'
			tSpouseRel = 'WIFE'
      end
		AddFamilyMember(tRel, ptrSpouseFam, ptrind)      
      
      -- Add Spouse
      if tab_Family.Spouse ~= nil then  
         -- Build Spouse
         ptrSpouse =  CreateInd(tab_Family.SpouseFullName, tab_Family.SpouseGender, 0, ptrSource)
    
    		-- Add Residence if exists
         if tab_Family.SpousePlc ~= nil then
            ptrSpouseRes = AddEvent(ptrSpouse, 'RESI', dAfterDate, CleanPlace(tab_Family.SpousePlc), ptrSource)
         end

         -- Add Spouse of family
         AddFamilyMember(tSpouseRel,ptrSpouseFam,ptrSpouse)
      end
      
      -- Add Children
      if tab_Family.NumChildren > 0 then
         for key2,ChildData in pairs(tab_Family.Children) do
            -- Add Individual Child
            local ptrChild =  CreateInd(ChildData['FullName'], ChildData['Gender'], 0 , ptrSource)
            AddFamilyMember('CHIL', ptrSpouseFam, ptrChild) 
            
            -- Add Residence if exists
            if ChildData['ResidencePlc'] ~= nil then
               local ptrChildRes = AddEvent(ptrChild, 'RESI', dAfterDate, CleanPlace(ChildData['ResidencePlc']), ptrSource)
            end        
         end
      end
   end

	ptrPartsFam = nil
	-- Add Parent(s) & Family
   if tab_Family.Child ~= nil then
      local dAfterDate = fhNewDate()
      dAfterDate:SetRange('after',dpDatePt)
      
      -- Create family
      ptrPartsFam = fhCreateItem("FAM") 
      
      -- Add main individual as child of family
		AddFamilyMember('CHIL', ptrPartsFam, ptrind)
      -- Set relationship type for the individual
      if tab_Family.Adopted then
         ptrFAMC = fhGetItemPtr(ptrind,'~.FAMC')
         ptrAdopted = fhCreateItem('PEDI', ptrFAMC)
         fhSetValueAsText(ptrAdopted, 'Adopted')
      else
         if tab_Family.Foster then
            ptrFAMC = fhGetItemPtr(ptrind,'~.FAMC')
            ptrAdopted = fhCreateItem('PEDI', ptrFAMC)
            fhSetValueAsText(ptrAdopted, 'Foster')
         end
      end
      
      -- Create Siblings
      if tab_Family.NumSiblings > 0 then
         for key2,SibData in pairs(tab_Family.Siblings) do
            -- Add Individual Sibling
            local ptrSibling =  CreateInd(SibData['FullName'], SibData['Gender'], 0 , ptrSource)
            AddFamilyMember('CHIL', ptrPartsFam, ptrSibling) 
            
            -- Add Residence if exists
            if SibData['ResidencePlc'] ~= nil then
               local ptrSibling1Res = AddEvent(ptrSibling, 'RESI', dAfterDate, CleanPlace(SibData['ResidencePlc']), ptrSource)
            end 
            
            -- Add Siblings Spouse if exists
            if SibData['SpouseName'] ~= nil then
               local ptrSiblingSpouse =  CreateInd(SibData['SpouseName'], SibData['SpouseGender'], 0 , ptrSource) 
               local ptrSpouseSibFam = fhCreateItem("FAM")
               AddFamilyMember('WIFE', ptrSpouseSibFam, ptrSibling)
               AddFamilyMember('HUSB', ptrSpouseSibFam, ptrSiblingSpouse)   
            end        
         end           
      end

      if tab_Family.Parent1 ~= nil then 
         -- Add Parent
         ptrParent1 =  CreateInd(tab_Family.Parent1, tab_Family.Parent1Gender, 0 , ptrSource)

         -- Add Residence if exists
         if tab_Family.ParentsPlc ~= nil then
            ptrParent1Res = AddEvent(ptrParent1, 'RESI', dAfterDate, CleanPlace(tab_Family.ParentsPlc), ptrSource)
         end
		
         -- Add to Family
         local tParRel = 'HUSB'
         if tab_Family.Parent1Gender == 'F' then
            tParRel = 'WIFE'
         elseif tab_Family.Parent2Gender == 'M' then
            tParRel = 'WIFE'
         end
         AddFamilyMember(tParRel, ptrPartsFam, ptrParent1)
		end

		-- Parent 2
		if tab_Family.Parent2 ~= nil then 
         -- Add Parent
			ptrParent2 =  CreateInd(tab_Family.Parent2, tab_Family.Parent2Gender, 0 , ptrSource)

			-- Add Residence if exists
			if tab_Family.ParentsPlc ~= nil then
            ptrParent1Res = AddEvent(ptrParent2, 'RESI', dAfterDate, CleanPlace(tab_Family.ParentsPlc), ptrSource)
         end
         
         local tParRel = 'WIFE' 
         if tab_Family.Parent2Gender == 'M' then
            tParRel = 'HUSB'
         elseif tab_Family.Parent1Gender == 'F' then
            tParRel = 'HUSB'
         end
			AddFamilyMember(tParRel,ptrPartsFam,ptrParent2)
		end
   end
	-- Add GrandParent(s) Family
   if tab_Family.GrandChild ~= nil then
      local dAfterDate = fhNewDate()
      dAfterDate:SetRange('after',dpDatePt)
      
      -- Create family
      ptrGrandPartsFam = fhCreateItem("FAM") 

      if tab_Family.GrandParent1 ~= nil then 
         -- Add GrandParent
         local ptrGrandParent1 =  CreateInd(tab_Family.GrandParent1, tab_Family.GrandParent1Gender, 0 , ptrSource)

         -- Add Residence if exists
         if tab_Family.GrandParentsPlc ~= nil then
            ptrGrandParent1Res = AddEvent(ptrGrandParent1, 'RESI', dAfterDate, CleanPlace(tab_Family.GrandParentsPlc), ptrSource)
         end
		
         -- Add to Family
         local tParRel = 'HUSB'
         if tab_Family.GrandParent1Gender == 'F' then
            tParRel = 'WIFE'
         elseif tab_Family.GrandParent2Gender == 'M' then
            tParRel = 'WIFE'
         end
         AddFamilyMember(tParRel, ptrGrandPartsFam, ptrGrandParent1)
		end

		-- GrandParent 2
		if tab_Family.GrandParent2 ~= nil then 
         -- Add GrandParent
			local ptrGrandParent2 =  CreateInd(tab_Family.GrandParent2, tab_Family.GrandParent2Gender, 0 , ptrSource)

			-- Add Residence if exists
			if tab_Family.ParentsPlc ~= nil then
            ptrGrandParent1Res = AddEvent(ptrGrandParent2, 'RESI', dAfterDate, CleanPlace(tab_Family.GrandParentsPlc), ptrSource)
         end
         
         local tParRel = 'WIFE' 
         if tab_Family.GrandParent2Gender == 'M' then
            tParRel = 'HUSB'
         elseif tab_Family.GrandParent1Gender == 'F' then
            tParRel = 'HUSB'
         end
			AddFamilyMember(tParRel,ptrGrandPartsFam,ptrGrandParent2)
		end
      
      -- Link to Parents
      if ptrPartsFam ~= nil then
         if tab_Family.GrandChildFather == true then
            AddFamilyMember('CHIL', ptrGrandPartsFam, ptrParent1)   
         else
            AddFamilyMember('CHIL', ptrGrandPartsFam, ptrParent2)
         end  
      else
         -- Create family
         ptrPartsFam = fhCreateItem("FAM") 
      
         -- Add main individual as child of family
         AddFamilyMember('CHIL', ptrPartsFam, ptrind)
         
         -- Dummy Parent
         local ptrParent =  nil
         if tab_Family.GrandChildFather == true then
            ptrParent =  CreateInd('/' .. captialise(tMainSurname) .. '/', 'M', 0 , ptrSource)
            AddFamilyMember('HUSB', ptrPartsFam, ptrParent)   
         else
            ptrParent =  CreateInd('//', 'F', 0 , ptrSource)
            AddFamilyMember('WIFE', ptrPartsFam, ptrParent)
         end
         AddFamilyMember('CHIL', ptrGrandPartsFam, ptrParent) 
      end  
   end   
end

Source:CWCG-Build-from-CSV1.fh_lua