Export Gedcom File.fh_lua

--[[
@Title:			Export Gedcom File
@Type:				Standard
@Author:			Mike Tate
@Contributors:	
@Version:			4.6
@Keywords:		
@LastUpdated:	24 Aug 2021
@Licence:			This plugin is copyright (c) 2021 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:	Converts current FH GEDCOM and Media files so they import into other genealogy programs using GEDCOM 5.5 or 5.5.1 formats.
					Handles all custom tags such as _LIST _ROOT _SENT _PLAC _SHAR _SHAN _FLGS _ATTR _USED _EMAIL _WEB _STAT _TYPE _NOTE _FILE _DATE _KEYS _AREA _EXCL _CAPT _RNOT _SRCT.
					Handles all standard tags such as FILE CHAR NICK GIVN SPFX SURN ASSO AGE DATE PHON FAX MAIL WWW ABBR TEXT OBJE REFN LANG MAP LATI LONG CHAN as required by different genealogy programs.
					Optionally converts characters into ANSI/CP1252, or ISO-8859-1 for TNG, or UTF-8 with/without BOM, or UTF-16 with/without BOM.
]]
--[=[
@V4.?:				TBD: Can doAnalyse() use FH API easier than GEDCOM file? TBD: Review GFT for Media Picture Note settings in several rules? TBD: Use fhCallBuiltInFunction("TextPart",strText,1,0,"TIDY"):gsub(" +"," "):gsub(" +,",",") in strTidyText(...)?
@V4.6:				CheckVersionInStore() at monthly intervals; Citation NOTE Weblink adds _LINK URL & fix copy Media to Citations for FTM; TBD: doPrune()? < > to < > substitution;
@V4.5:				Aggressive memory management and Result Set counts for big Projects; Fixed strPref:gsub(...) for % captures; FSO:CopyFile() for unaltered media; Repository metafield to labelled Note as well as generic link;
@V4.5:				Lookup default font size & name in Registry for doPrune() function HTML mode; Convert Residence (family) for most GEDCOM 5.5 products; doKeyword() TNG _KEY to _TYPE omit Note; Let media errors be inhibited and log to Result Set;
@V4.4:				Fix GWC FACT; Fix GST for FH V6 and _FMT and _SDATE and Family _ATTR;
@V4.3:				Fix rich text reserved/escaped characters, align, indent & table tags for HTML 5 in more efficient doPrune();
@V4.2:				Fix Note SOUR citations for TNG; Add collectgarbage("step",0); Project only Fact Sets; Rich text FTF to HTML repairs in doPrune(); Fix accent chars in file path; Drop Sort Dates from FTM; Reformat Templated Source Title & Short Title in doSrcTempLnk(); Handle Templated Citation Fields;
@V4.1:				Plugin Store Help no -introduction; Fix SOUR FAMILY_HISTORIAN for FH6/7 & GST; Fix vanishing Media and Link/Notes for FILE-REL/ABS; 
@V4.0:				Update (MFT) My Family Tree; (AQP) Ancestral Quest Program developer updates; (RFT) RootsFinder Family Tree website investigation; TBD: GedSite et al INDI.FAMC.PEDI to FAM.CHIL._FREL/_MREL;
@V4.0:				Updated library to Functions Prototypes v3.0; iup_gui.Balloon = "NO" for PlayOnLinux/Mac; [[private]] spanning 0 @N1@ NOTE and CONT; TNG Keywords; FILE~ABS & FILE~REL capitalise Media folder in FILE link; FH V7 GEDCOM 5.5.1, Templated Sources, Source Templates, Research Notes;
@V4.0:				Fix bug in doCust_Attr(strVal); Let Export GEDCOM File have FILE tags; FAMILY-HISTORIAN in doHeadSour(); Prune invalid chars; (LFT) UTF8; (RMT) update; Improve the [[private]] text option wording; Rename (Sdr) not Draft;
@V3.9:				Fix bug in doCharCode(strText); Partial update to (RFT) RootsFinder Family Tree versus KB Plugin Help & Advice.
@V3.8:				(FTA) FT Analyzer > Extra Options > Place Record: Keep Custom Record; (HER) Heredis 2019 updates; (CEB) Custom Export Beta defaults to (Sdr); new modes (AGS) Ancestris Genealogy Software with 5.5.1 Place Map Lat/Long, (AQP) Ancestral Quest Program, (RFT) RootsFinder Family Tree.
@V3.7:				Warning prompt before Restore Defaults and delete .dat file, give [unnnamed person] a Surname /?/, fix bug in doRecordRule(...) that mishandles File Root copied INDI Record header, allow max width/height & JPEG to apply to all files, fix im.FileImageSave() errors & convert all file types.
@V3.6:				Fix doFactDefs() for accented chars, fix Companion Used rule for 1 _USED in TNG, fix DayNumber problems, updated library with GetDayNumber(), etc.
@V3.5:				Update for LMO Sort Dates & remove them, and improve Date handling for Before & After, etc. (See the Order Facts by Sort Date plugin)
@V3.4:				Fix doSortDates() when Preference = "mmm dd, yyy" & for Family EVENt & for Invalid Dates, and prioritise out-of-order explicit Dates, make Ancestry convert CENS to RESI for Census hints, fix Media .Used issue found by Bill Murphy.
@V3.3:				(Fixed iup_gui freeze!) Tidy Place & Address, Export Fact Def, Synthetic Prefix, Avoid Repeat Captions updates, new Label Options tab, Full/Brief option, change  to Whole Record Citation, omit HUSB/WIFE AGE from Witness Role in doSaveFacts(), fix orphan _ASID, Link/Note Captions, et al,
@V3.3:				(TNG/RMT/etc) _PRIM/_PROF/_PHOTO for all Media modes, (ANC) Ancestry doCiteNote() fix, (FMP) ZipFMP\ZipFMP.zip, (FST) Family Search ADDR > PLAC, (FTD) Family Tree DNA event tag doConvert(), (FTM-) without FTM Name Tag, (GST+ RMT+) doSortDates(), (RMT) _PRIM,_PLAC,EMAIL,WWW, (GWC) GEDmill Website Creator, (LFT) _ATTR Event & Value + _STAT + doContacts()
@V3.2:				(GST) UTF-8 by default, no ADDRess tidy, [private] option, (.tif to .jpg in Export folder?), doCopyFile() for big binary files/videos, (MYH) MyHeritage, (TPT) TribalPages, (FST) Family Search (placeholder)
@V3.1:				Add (GST) GedSite, (TPT) TribalPages Tree placeholder, (TNG) UTF-8 by default, improve (GRT) Genes Reunited Birth/Death Date handler, and latest library.
@V3.0:				Fix standalone Gedcom MakeFolder(PublicPath) problem.
@V2.9:				Add global Backup folder sticky setting to act as default for local Backup folder, new 'Keep Media folders' option, 'Exclude from Diagrams' inhibits TNG _PRIM Y and FTM _PHOTO tags, export Draft 5.5.1 modes removed & Media _DATE used for Legacy.
@V2.8:				Adds (Sdr) Standard GEDCOM Draft 5.5.1 and (Mdr) Minimum GEDCOM Draft 5.5.1 export modes, fixes doAnalyse() FAMily and other record name problems, fixes sticky settings not correctly synchronised via OneDrive, Dropbox, etc.
@V2.7:				Adjustment for Heredis on iOS Note Records.
@V2.6:				Supports same sex HUSB/WIFE relationship asymmetric format option, UNICODE U+10000 to U+10FFFF Supplementary Planes, (LFT) Legacy Note record tabs Research & Medical, fixed Caption Note for FILE~REL mode, added Draft 5.5.1 FORM tag.
@V2.5:				ASSO + RELA + TYPE set for Custom Event + Note for ANC, LFT & RWW. Media Picture Note v Link/Note updated in all modes.
@V2.4:				Export to (RMT) RootsMagic: remove 1 CHAN, make 1 RIN an EVENt, elevate 1 NOTE & 2 _SHAR Sources, move NOTE.REFN to Note, use 1 SOUR ROOTSMAGIC, and (All) removes 1 DEST & 1 COPR from HEAD, and allows FAMily 1 RIN (but RIN are in any record).
@V2.3:				Inhibit TNG Place Tidy, fix CONC/CONT tags, etc, in doSaveASID() and strCreateMedia(), add isPrivate to exclude private Notes, etc, in double square brackets, and other bug fixes.
@V2.3:				(TPF) TribalPages Family Tree? Replace dicNameTag[strTag] with doNameTag(strTag) to allow strTag to be a UDF in function doRepoNote() and elsewhere.
@V2.2:				Fix all records with no CHANge tag e.g. doShareFact(), move Export to Public for Projects, both as requested by Simon Orde, (ANC) Ancestry Family Tree, (FMP) FindMyPast Family Tree.
@V2.1:				Fix OBJE record with no CHANge tag, (TNG) The Next Generation updates for _PRIM Y and _TYPE media tags, (GSP) GedStar Pro v4.5.6, (ZPG) ZoomPast updates.
@V2.0:				(GSP) GedStar Pro, (LFT) Legacy, (RMT) RootsMagic, (ZPG) ZoomPast updates, new Other Options tab, settings preserved despite new Rules, synthetic Facts for Witnessed/Shared Events, fixed bug with adding Note/Source/Fact for same Individual. 
@V1.9:				doCitation() moves NOTE instances & subsidiary tags in Name Tag format to Text From Source, fixes handling of Witness Source subsidiary tags, adds (HER) Heredis by BSD Concept options.
@V1.8:				Prevent corrupt media files error in doCopyFile(*) function, allow record without CHANge tag, doDelete() line from record, new CHAR UNICODE for UTF-16, media options, (FTM) Family Tree Maker & (GSP) GedStar Pro for Android options, bug fixes.
@V1.7:				Place Record ~ Delete Record Ident (TNG) option added, plus revised general and encoder library modules.
@V1.6:				Save/LoadFolder() with relative paths, cater for Media ASID missing, handle _PLAC records, Witness _SHAR & _SHAN tags, Family Historian 5 export mode, AFILE/RFILE absolute/relative FILE modes.
@V1.5:				For FH V6 & IUP V3.11.2, HelpDialogue conditional ExpandChildren="YES/NO", RefreshDialogue uses NaturalSize, handle UTF-16 and V5 ANSI Gedcom.
@V1.4:				Cope if no File Root, improve ISO "tng" case, add JPEG for frames toggle, check if AREA outside image, improve Max rule use, closing option to view GEDCOM with FH or Notepad or Explorer, fixed a few bugs.
@V1.3:				Updates for FTA & GFT, converts all Media, plus minor improvements.
@V1.2:				First Plugin Store Version.
@V1.1:				New GUI, Help & Advice pages, Result Set, and record by record processing.
@V1.0:				Derived fron Export Gedcom to TNG V1.6.
]=]

StrLessLess = ""																-- Legacy 9 rich text delimiters
StrMoreMore = ""
StrBullet   = ""																-- Bullet Blob for lists
StrSection  = ""																-- Section Sign is @ substitute
StrPilcrow = "  "																-- Newline Pilcrow symbol
if fhGetAppVersion() > 5 then
	fhSetStringEncoding("UTF-8")
	StrLessLess = fhConvertANSItoUTF8(StrLessLess)
	StrMoreMore = fhConvertANSItoUTF8(StrMoreMore)
	StrBullet   = fhConvertANSItoUTF8(StrBullet)
	StrSection  = fhConvertANSItoUTF8(StrSection)
	StrPilcrow  = fhConvertANSItoUTF8(StrPilcrow)
end
StrLessTagMore = StrLessLess.."%1"..StrMoreMore							-- HTML tag replacement "%1"	-- V4.0

--[[
@Title:			aa Library Functions Preamble
@Author:			Mike Tate
@Version:			3.1
@LastUpdated:	22 Jun 2021
@Description:	All the library functions prototype closures for Plugins.
]]

--[[
@Module:			+fh+stringx_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	19 Sep 2020
@Description:	Extended string functions to supplement LUA string library.
@V3.0:				Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility; Added inert(strTxt) function;
@V2.5:				Support FH V6 Encoding = UTF-8;
@V2.4:				Tolerant of integer & nil parameters just link match & gsub;
@V1.0:				Initial version.
]]

local function stringx_v3()

	local fh = {}									-- Local environment table

	-- Supply current file encoding format --
	function fh.encoding()
		if fhGetAppVersion() > 5 then return fhGetStringEncoding() end
		return "ANSI"
	end -- function encoding

	-- Split a string using "," or chosen separator --
	function fh.split(strTxt,strSep)
		local tblFields = {}
		local strPattern = string.format("([^%s]+)", strSep or ",")
		strTxt = tostring(strTxt or "")
		strTxt:gsub(strPattern, function(strField) tblFields[#tblFields+1] = strField end)
		return tblFields
	end -- function split

	-- Split a string into numbers using " " or "," or "x" separators	-- Any non-number remains as a string
	function fh.splitnumbers(strTxt)
		local tblNum = {}
		strTxt = tostring(strTxt or "")
		strTxt:gsub("([^ ,x]+)", function(strNum) tblNum[#tblNum+1] = tonumber(strNum) or strNum end)
		return tblNum
	end -- function splitnumbers

	local strMagic = "([%^%$%(%)%%%.%[%]%*%+%-%?])"							-- UTF-8 replacement for "(%W)"

	-- Hide magic pattern symbols	^ $ ( ) % . [ ] * + - ?
	function fh.plain(strTxt)
		-- Prefix every magic pattern character with a % escape character,
		-- where %% is the % escape, and %1 is the original character capture.
		strTxt = tostring(strTxt or ""):gsub(strMagic,"%%%1")
		return strTxt
	end -- function plain

	-- matches is plain text version of string.match()
	function fh.matches(strTxt,strFind,intInit)
		strFind = tostring(strFind or ""):gsub(strMagic,"%%%1")			-- Hide magic pattern symbols
		return tostring(strTxt or ""):match(strFind,tonumber(intInit))
	end -- function matches

	-- replace is plain text version of string.gsub()
	function fh.replace(strTxt,strOld,strNew,intNum)
		strOld = tostring(strOld or ""):gsub(strMagic,"%%%1")				-- Hide magic pattern symbols
		return tostring(strTxt or ""):gsub(strOld,function() return strNew end,tonumber(intNum))	-- Hide % capture symbols
	end -- function replace

	-- Hide % escape/capture symbols in replacement so they are inert
	function fh.inert(strTxt)
		strTxt = tostring(strTxt or ""):gsub("%%","%%%%")					-- Hide all % symbols
		return strTxt
	end -- function inert

	-- convert is pattern without captures version of string.gsub()
	function fh.convert(strTxt,strOld,strNew,intNum)
		return tostring(strTxt or ""):gsub(tostring(strOld or ""),function() return strNew end,tonumber(intNum))	-- Hide % capture symbols
	end -- function convert

	local dicUpper = { }
	local dicLower = { }
	local dicCaseX = { }
	-- ASCII unaccented letter translations for Upper, Lower, and Case Insensitive
	for intUpper = string.byte("A"), string.byte("Z") do
		local strUpper = string.char(intUpper)
		local strLower = string.char(intUpper - string.byte("A") + string.byte("a"))
		dicUpper[strLower] = strUpper
		dicLower[strUpper] = strLower
		local strCaseX = "["..strUpper..strLower.."]"
		dicCaseX[strLower] = strCaseX
		dicCaseX[strUpper] = strCaseX
	end

	-- Supply character length of ANSI text --
	function fh.length(strTxt)
		return string.len(strTxt or "")
	end -- function length

	-- Supply character substring of ANSI text --
	function fh.substring(strTxt,i,j)
		return string.sub(strTxt or "",i,j)
	end -- function substring

	-- Translate upper/lower case ANSI letters to pattern that matches both --
	function fh.caseless(strTxt)
		strTxt = tostring(strTxt or ""):gsub("[A-Za-z]",dicCaseX)
		return strTxt
	end -- function caseless

	if fh.encoding() == "UTF-8" then

		-- Supply character length of UTF-8 text --
		function fh.length(strTxt)
			isFlag = fhIsConversionLossFlagSet()
			strTxt = fhConvertUTF8toANSI(strTxt or "")
			fhSetConversionLossFlag(isFlag)
			return string.len(strTxt)
		end -- function length

		local strUTF8 = "([%z\1-\127\194-\244][\128-\191]*)"			-- Cater for Lua 5.1 %z or Lua 5.3 \0
		if fhGetAppVersion() > 6 then
			strUTF8 = "([\0-\127\194-\244][\128-\191]*)"
		end

		-- Supply character substring of UTF-8 text --
		function fh.substring(strTxt,i,j)
			local strSub = ""
			j = j or -1
			if j < 0 then j = j + length(strTxt) + 1 end
			if i < 0 then i = i + length(strTxt) + 1 end
			for strChr in string.gmatch(strTxt or "",strUTF8) do
				if j <= 0 then break end
				j = j - 1
				i = i - 1
				if i <= 0 then strSub = strSub..strChr end
			end
			return strSub
		end -- function substring

		-- Translate lower case to upper case UTF-8 letters --
		function fh.upper(strTxt)
			strTxt = tostring(strTxt or ""):gsub("([a-z\194-\244][\128-\191]*)",dicUpper)
			return strTxt
		end -- function upper

		-- Translate upper case to lower case UTF-8 letters --
		function fh.lower(strTxt)
			strTxt = tostring(strTxt or ""):gsub("([A-Z\194-\244][\128-\191]*)",dicLower)
			return strTxt
		end -- function lower

		-- Translate upper/lower case UTF-8 letters to pattern that matches both --
		function fh.caseless(strTxt)
			strTxt = tostring(strTxt or ""):gsub("([A-Za-z\194-\244][\128-\191]*)",dicCaseX)
			return strTxt
		end -- function caseless

		-- Following tables use ASCII numeric coding to be immune from ANSI/UTF-8 encoding --

		local arrPairs =	-- Upper & Lower case groups of UTF-8 letters with same prefix --
		{--	{ Prefix; Beg ; End ; Inc; Offset Upper > Lower };			-- These include all ANSI letters and many more
			{ "\195"; 0x80; 0x96;  1 ; 32 };								-- 195=0xC3  U+00C0 to  U+00D6 and  U+00E0 to  U+00F6
			{ "\195"; 0x98; 0x9E;  1 ; 32 };								-- 195=0xC3  U+00D8 to  U+00DE and  U+00F8 to  U+00FE
			{ "\196"; 0x80; 0xB6;  2 ;  1 };								-- 196=0xC4 A U+0100 to k U+0137 in pairs
			{ "\196"; 0xB9; 0xBD;  2 ;  1 };								-- 196=0xC4 L U+0139 to l U+013E in pairs
			{ "\197"; 0x81; 0x87;  2 ;  1 };								-- 197=0xC5 L U+0141 to n U+0148 in pairs
			{ "\197"; 0x8A; 0xB6;  2 ;  1 };								-- 197=0xC5 ? U+014A to y U+0177 in pairs
			{ "\197"; 0xB9; 0xBD;  2 ;  1 };								-- 197=0xC5 Z U+0179 to  U+017E in pairs
			{ "\198"; 0x82; 0x84;  2 ;  1 };								-- 198=0xC6 ?  U+0182 to ?  U+0185 in pairs
			-- Add more Unicode groups here as usage increases --
		}
		local dicPairs =	-- Upper v Lower case UTF-8 letters that don't fit groups above --
		{	[string.char(0xC4,0xBF)] = string.char(0xC5,0x80);			-- ? U+013F and ? U+0140
			[string.char(0xC5,0xB8)] = string.char(0xC3,0xBF);			--  U+0178 and  U+00FF
		}

		local intBeg1 = string.byte(string.sub("",1))
		local intBeg2 = string.byte(string.sub("",2))
		local intEnd1 = string.byte(string.sub("Z",1))
		local intEnd2 = string.byte(string.sub("Z",2))
	--	print(string.format("%#x %#x %#x %#x",intBeg1,intBeg2,intEnd1,intEnd2)) -- Useful to work out numeric coding

		-- Populate the UTF-8 letter translation dictionaries --
		for intGroup, tblGroup in ipairs ( arrPairs ) do				-- UTF-8 accented letter groups
			local strPrefix = tblGroup[1]
			for intUpper = tblGroup[2], tblGroup[3], tblGroup[4] do
				local strUpper = string.char(intUpper)
				local strLower = string.char(intUpper + tblGroup[5])
				local strCaseX = strPrefix.."["..strUpper..strLower.."]"
				strUpper = strPrefix..strUpper
				strLower = strPrefix..strLower
				dicUpper[strLower] = strUpper
				dicLower[strUpper] = strLower
				dicCaseX[strLower] = strCaseX
				dicCaseX[strUpper] = strCaseX
			end
		end
		for strUpper, strLower in pairs ( dicPairs ) do					-- UTF-8 accented letters where upper & lower have different prefix
			dicUpper[strLower] = strUpper
			dicLower[strUpper] = strLower
			local strCaseX = ""
			for intByte = 1, #strUpper do									-- Matches more than just the two letters, but can't do any better
				strCaseX = strCaseX.."["..strUpper:sub(intByte,intByte)..strLower:sub(intByte,intByte).."]"
			end
			dicCaseX[strLower] = strCaseX
			dicCaseX[strUpper] = strCaseX
		end

	end

	-- overload fh functions into string table
	for strIndex, anyValue in pairs(fh) do
		if type(anyValue) == "function" then
			string[strIndex] = anyValue
		end
	end

	return fh

end -- local function stringx_v3

local stringx = stringx_v3()													-- To access FH string extension module

--[[
@Module:			+fh+tablex_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	25 Aug 2020
@Description:	A Table Load Save Module.
@V3.0:				Function Prototype Closure version.
@V1.2:				Added local definitions of _ to ensure nil gets returned on error.
@V1.1:				?
@V1.0:				Initial version 0.94 is Lua 5.1 compatible.
]]

local function tablex_v3()

	local fh = {}									-- Local environment table

	------------------------------------------------------ Start Table Load Save
	-- require "_tableloadsave"
	--[[
   Save Table to File/Stringtable
   Load Table from File/Stringtable
   v 0.94
   
   Lua 5.1 compatible
   
   Userdata and indices of these are not saved
   Functions are saved via string.dump, so make sure it has no upvalues
   References are saved
   ----------------------------------------------------
   table.save( table [, filename] )
   
   Saves a table so it can be called via the table.load function again
   table must a object of type 'table'
   filename is optional, and may be a string representing a filename or true/1
   
   table.save( table )
      on success: returns a string representing the table (stringtable)
      (uses a string as buffer, ideal for smaller tables)
   table.save( table, true or 1 )
      on success: returns a string representing the table (stringtable)
      (uses io.tmpfile() as buffer, ideal for bigger tables)
   table.save( table, "filename" )
      on success: returns 1
      (saves the table to file "filename")
   on failure: returns as second argument an error msg
   ----------------------------------------------------
   table.load( filename or stringtable )
   
   Loads a table that has been saved via the table.save function
   
   on success: returns a previously saved table
   on failure: returns as second argument an error msg
   ----------------------------------------------------
   
   chillcode, http://lua-users.org/wiki/SaveTableToFile
   Licensed under the same terms as Lua itself.
	]]--

   -- declare local variables
   --// exportstring( string )
   --// returns a "Lua" portable version of the string
   local function exportstring( s )
      s = string.format( "%q",s )
      -- to replace
      s = string.gsub( s,"\\\n","\\n" )
      s = string.gsub( s,"\r","\\r" )
      s = string.gsub( s,string.char(26),"\"..string.char(26)..\"" )
      return s
   end
--// The Save Function
function fh.save( tbl,filename )
   local charS,charE = "   ","\n"
   local file,err,_ 					-- V1.2 -- Added _
   -- create a pseudo file that writes to a string and return the string
   if not filename then
      file =  { write = function( self,newstr ) self.str = self.str..newstr end, str = "" }
      charS,charE = "",""
   -- write table to tmpfile
   elseif filename == true or filename == 1 then
      charS,charE,file = "","",io.tmpfile()
   -- write table to file
   -- use io.open here rather than io.output, since in windows when clicking on a file opened with io.output will create an error
   else
      file,err = io.open( filename, "w" )
      if err then return _,err end
   end
   -- initiate variables for save procedure
   local tables,lookup = { tbl },{ [tbl] = 1 }
   file:write( "return {"..charE )
   for idx,t in ipairs( tables ) do
      if filename and filename ~= true and filename ~= 1 then
         file:write( "-- Table: {"..idx.."}"..charE )
      end
      file:write( "{"..charE )
      local thandled = {}
      for i,v in ipairs( t ) do
         thandled[i] = true
         -- escape functions and userdata
         if type( v ) ~= "userdata" then
            -- only handle value
            if type( v ) == "table" then
               if not lookup[v] then
                  table.insert( tables, v )
                  lookup[v] = #tables
               end
               file:write( charS.."{"..lookup[v].."},"..charE )
            elseif type( v ) == "function" then
               file:write( charS.."loadstring("..exportstring(string.dump( v )).."),"..charE )
            else
               local value =  ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
               file:write(  charS..value..","..charE )
            end
         end
      end
      for i,v in pairs( t ) do
         -- escape functions and userdata
         if (not thandled[i]) and type( v ) ~= "userdata" then
            -- handle index
            if type( i ) == "table" then
               if not lookup[i] then
                  table.insert( tables,i )
                  lookup[i] = #tables
               end
               file:write( charS.."[{"..lookup[i].."}]=" )
            else
               local index = ( type( i ) == "string" and "["..exportstring( i ).."]" ) or string.format( "[%d]",i )
               file:write( charS..index.."=" )
            end
            -- handle value
            if type( v ) == "table" then
               if not lookup[v] then
                  table.insert( tables,v )
                  lookup[v] = #tables
               end
               file:write( "{"..lookup[v].."},"..charE )
            elseif type( v ) == "function" then
               file:write( "loadstring("..exportstring(string.dump( v )).."),"..charE )
            else
               local value =  ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
               file:write( value..","..charE )
            end
         end
      end
      file:write( "},"..charE )
   end
   file:write( "}" )
   -- Return Values
   -- return stringtable from string
   if not filename then
      -- set marker for stringtable
      return file.str.."--|"
   -- return stringttable from file
   elseif filename == true or filename == 1 then
      file:seek ( "set" )
      -- no need to close file, it gets closed and removed automatically
      -- set marker for stringtable
      return file:read( "*a" ).."--|"
   -- close file and return 1
   else
      file:close()
      return 1
   end
end

--// The Load Function
function fh.load( sfile )
   local tables,err,_ 					-- V1.2 -- Added _
   -- catch marker for stringtable
   if string.sub( sfile,-3,-1 ) == "--|" then
      tables,err = loadstring( sfile )
   else
      tables,err = loadfile( sfile )
   end
   if err then return _,err
   end
   tables = tables()
   for idx = 1,#tables do
      local tolinkv,tolinki = {},{}
      for i,v in pairs( tables[idx] ) do
         if type( v ) == "table" and tables[v[1]] then
            table.insert( tolinkv,{ i,tables[v[1]] } )
         end
         if type( i ) == "table" and tables[i[1]] then
            table.insert( tolinki,{ i,tables[i[1]] } )
         end
      end
      -- link values, first due to possible changes of indices
      for _,v in ipairs( tolinkv ) do
         tables[idx][v[1]] = v[2]
      end
      -- link indices
      for _,v in ipairs( tolinki ) do
         tables[idx][v[2]],tables[idx][v[1]] =  tables[idx][v[1]],nil
      end
   end
   return tables[1]
end

------------------------------------------------------ End Table Load Save

	-- overload fh functions into table
	for strIndex, anyValue in pairs(fh) do
		if type(anyValue) == "function" then
			table[strIndex] = anyValue
		end
	end

	return fh

end -- local function tablex_v3

local tablex  = tablex_v3 ()													-- To access FH table extension module

--[[
@Module:			+fh+iterate_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	25 Aug 2020
@Description:	An iterater functions module to supplement LUA functions.
@V3.0:				Function Prototype Closure version.
@V1.2:				RecordTypes() includes HEAD tag.
@V1.1:				?
@V1.0:				Initial version.
]]

local function iterate_v3()

	local fh = {}																-- Local environment table

	-- Iterator for all records of one chosen type --
	function fh.Records(strType)
		local ptrAll = fhNewItemPtr()										-- Pointer to all records in turn
		local ptrRec = fhNewItemPtr()										-- Pointer to record returned to user
		ptrAll:MoveToFirstRecord(strType)
		return function ()
			ptrRec:MoveTo(ptrAll)
			ptrAll:MoveNext()
			if ptrRec:IsNotNull() then return ptrRec end
		end
	end -- function Records

	-- Iterator for all the record types --
	function fh.RecordTypes()
		local intNext = -1														-- Next record type number
		local intLast = fhGetRecordTypeCount()								-- Last record type number
		return function()
			intNext = intNext + 1
			if intNext == 0 then												-- Includes HEAD tag -- V1.2
				return "HEAD"
			elseif intNext <= intLast then
				return fhGetRecordTypeTag(intNext)							-- Return record type tag
			end
		end
	end -- function RecordTypes

	-- Iterator for all items in all records of chosen types --
	function fh.Items(...)
		local arg = {...}
		local intType = 1														-- Integer record type number
		local tblType = {}														-- Table of record type tags
		local ptrNext = fhNewItemPtr()										-- Pointer to next item in turn
		local ptrItem = fhNewItemPtr()										-- Pointer to item returned to user

		if #arg == 0 then
			for intType = 1, fhGetRecordTypeCount() do					-- No parameters so use all record types
				tblType[intType] = fhGetRecordTypeTag(intType)
			end
		else
			tblType = arg														-- Got parameters so use them instead
		end
	--	print(tblType[intType],intType)
		ptrNext:MoveToFirstRecord(tblType[intType])						-- Get first record of first type

		return function()
			repeat
				while ptrNext:IsNotNull() do									-- Loop through all items
					ptrItem:MoveTo(ptrNext)
					ptrNext:MoveNextSpecial()
					if ptrItem:IsNotNull() then return ptrItem end
				end
				intType = intType + 1											-- Loop through each record type
				if intType <= #tblType then
					ptrNext:MoveToFirstRecord(tblType[intType])
				end
			until intType > #tblType
		end
	end -- function Items

	-- Iterator for all facts of an individual --
	function fh.Facts(ptrIndi)
		local ptrItem = fhNewItemPtr()										-- Pointer to each item at level 1
		local ptrFact = fhNewItemPtr()										-- Pointer to each fact returned to user
		ptrItem:MoveToFirstChildItem(ptrIndi)
		return function ()
			while ptrItem:IsNotNull() do
				ptrFact:MoveTo(ptrItem)
				ptrItem:MoveNext()
				if fhIsFact(ptrFact) then return ptrFact end
			end
		end
	end -- function Facts

	return fh

end -- local function iterate_v3

local iterate = iterate_v3()													-- To access FH iterate items module

--[[
@Module:			+fh+general_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	25 Aug 2020
@Description:	A general functions module to supplement LUA functions, where all filenames are in ANSI.
@V3.0:				Function Prototype Closure version; GetDayNumber() error message reasons;
@V1.5:				Revised SplitFilename(strFilename) for missing extension.
@V1.4:				Revised EstimatedBirthDates() & EstimatedDeathDates() to fix null Dates.
@V1.3:				Add GetDayNumber(), EstimatedBirthDates(), EstimatedDeathDates().
@V1.2:				SplitFilename() updated for directory only paths, and MakeFolder() added.
@V1.1:				pl.path experiment revoked. New DirTree with omit branch option. Avoid using stringx_v2.
@V1.0:				Initial version.
]]

local function general_v3()

	local fh = {}													-- Local environment table

	require "lfs"													-- To access LUA filing system

	if fhGetAppVersion() > 6 then
		unpack = table.unpack
	end

	-- Check if file exists --
	function fh.FlgFileExists(strFileName)
		return lfs.attributes(strFileName,"mode") == "file"
		--[=[
		if lfs.attributes(strFileName,"mode") == "file" then
			return true
		else
			return false
		end
		--]=]
	end -- function FlgFileExists

	-- Check if folder exists --
	function fh.FlgFolderExists(strFolderName)
		return lfs.attributes(strFolderName:gsub("\\$",""),"mode") == "directory"
		--[=[
		if lfs.attributes(strFolderName:gsub("\\$",""),"mode") == "directory" then
			return true
		else
			return false
		end
		--]=]
	end -- function FlgFolderExists

	-- Check if folder writable --
	function fh.FlgFolderWrite(strFolderName)
		if fh.FlgFolderExists(strFolderName) then
			local fileHandle, strError = io.open(strFolderName.."\\xyz.xyz","w")
			if fileHandle ~= nil then
				fileHandle:close()
				os.remove(strFolderName.."\\xyz.xyz")
				return true
			end
		end
		return false
	end -- function FlgFolderWrite

	-- Open File and return Handle --
	function fh.OpenFile(strFileName,strMode)
		local fileHandle, strError = io.open(strFileName,strMode)
		if fileHandle == nil then
			error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..strError.." \n")
		end
		return fileHandle
	end -- function OpenFile

	-- Save string to file --
	function fh.SaveStringToFile(strString,strFileName)
		local fileHandle = fh.OpenFile(strFileName,"w")
		fileHandle:write(strString)
		assert(fileHandle:close())
	end -- function SaveStringToFile

	-- Load string from file --
	function fh.StrLoadFromFile(strFileName)
		local fileHandle = fh.OpenFile(strFileName,"r")
		local strString = fileHandle:read("*all")
		assert(fileHandle:close())
		return strString
	end -- function StrLoadFromFile

	-- Returns the Path, Filename, and Extension as 3 values
	function fh.SplitFilename(strFilename)
		if lfs.attributes(strFilename,"mode") == "directory" then
			local strPath = strFilename:gsub("[\\/]$","")
			return strPath.."\\","",""
		end
		strFilename = strFilename.."."
		return strFilename:match("^(.-)([^\\/]-%.([^\\/%.]-))%.?$")
	end -- function SplitFilename

	-- Return a Directory Tree entry & attributes on each iteration --
	function fh.DirTree(strDir,...)
		local arg = {...}
		assert(strDir and strDir ~= "", "directory parameter is missing or empty")
		if strDir:sub(-1) == "/"
		or strDir:sub(-1) == "\\" then
			strDir = strDir:sub(1,-2)								-- Remove trailing "/" or "\"
		end
    
		local function doYieldTree(strDir)
			for strEntry in lfs.dir(strDir) do
				if strEntry ~= "." and strEntry ~= ".." then
					strEntry = strDir.."\\"..strEntry
					local tblAttr, strError = lfs.attributes(strEntry)
					if not tblAttr then tblAttr = { mode="attrfail"; error=strError; } end 
					coroutine.yield(strEntry,tblAttr)
					if tblAttr.mode == "directory" then
						local isOK = true
						for intOmit, strOmit in ipairs (arg) do
							if strEntry:matches(strOmit) then	-- Omit tree branch
								isOK = false
								break
							end
						end
						if isOK then doYieldTree(strEntry) end
					end
				end
			end
		end -- local function doYieldTree

		return coroutine.wrap(function() doYieldTree(strDir) end)
	end -- function DirTree

	local function strErrorText(strError,strFileName,intRepeat)
		return strError:gsub(strFileName:match("(.+\\).+"),"Del#"..tostring(intRepeat)..":")
	end -- local function strErrorText

	-- Delete file if it exists --
	function fh.DeleteFile(strFileName,errFunction)
		if fh.FlgFileExists(strFileName) then
			local fileHandle, strError = os.remove(strFileName)
			if fileHandle == nil then
				local intRepeat = 1
				repeat
					if intRepeat > 1 and type(errFunction) == "function" then
						errFunction(strErrorText(strError,strFileName,intRepeat))
					end
					fhSleep(300,100)
					if fh.FlgFileExists(strFileName) then
						fileHandle, strError = os.remove(strFileName)
					end
					intRepeat = intRepeat + 1
				until fileHandle ~= nil or intRepeat > 10
				if intRepeat > 10 then error(strErrorText(strError,strFileName,intRepeat)) end
			end
		end
	end -- function DeleteFile

	-- Make subfolder if does not exist --
	function fh.MakeFolder(strFolder,errFunction)
		if not fh.FlgFolderExists(strFolder) then
			local isOK, strError = lfs.mkdir(strFolder)
			if not isOK then
				local strMessage = "Cannot Make Folder: "..tostring(strError)..".\n"..strFolder.."\n"
				if type(errFunction) == "function" then
					errFunction(strMessage)
				else
					error(strMessage)
				end
				return false
			end
		end
		return true
	end -- function MakeFolder

	-- Invoke FH Shell Execute API --
	function fh.DoExecute(strExecutable,...)
		local arg = {...}
		local errFunction = fhMessageBox
		if type(arg[#arg]) == 'function' then
			errFunction = arg[#arg]
			table.remove(arg)
		end
		if fhGetAppVersion() > 5 and fhGetStringEncoding() == "UTF-8" then
			strExecutable = fhConvertANSItoUTF8(strExecutable)
		end
		local isOK, intErrorCode, strErrorText = fhShellExecute(strExecutable,unpack(arg))
		if not isOK then
			errFunction(tostring(strErrorText).." ("..tostring(intErrorCode)..")")
		end
		return isOK
	end -- function DoExecute

	-- Obtain the Day Number for any Date Point --									-- Fix problems with invalid dates in DayNumber function
	function fh.GetDayNumber(datDate)
		if datDate:IsNull() then return 0 end
		local intDay = fhCallBuiltInFunction("DayNumber",datDate) 				-- Only works for Gregorian dates that were not skipped nor BC dates
		if not intDay then
			local strError = "because "													-- Error message reason	-- V3.0
			local calendar = datDate:GetCalendar()
			local oldMonth = datDate:GetMonth()
			local oldDayNo = datDate:GetDay()
			local intMonth = math.min( oldMonth, 12 ) 								-- Limit month to 12, and day to last of each month
			local intDayNo = math.min( oldDayNo, ({0;31;28;31;30;31;30;31;31;30;31;30;31;})[intMonth+1] )
			local intYear  = datDate:GetYear()
			if oldDayNo > intDayNo then strError = strError.."day "..oldDayNo.." too big "   end
			if oldMonth > intMonth then strError = strError.."month "..oldMonth.." too big " end
			if calendar == "Hebrew" and intYear > 3761 then
				intYear = intYear - 3761
				strError = strError.."Hebrew year > 3761 "
			elseif calendar ~= "Gregorian" then
				strError = strError..calendar.." disallowed "
			end
			if     intYear == 1752 and intMonth ==  9 and intDayNo <= 13 then	-- Use 2 Sep 1752 for 3 - 13 Sep 1752 dates skipped
				intDayNo = 2
				strError = strError.."3 - 13 Sep 1752 skipped "
			elseif intYear == 1582 and intMonth == 10 and intDayNo <= 14 then	-- Use 4 Oct 1582 for 5 - 14 Oct 1582 dates skipped
				intDayNo = 4
				strError = strError.."5 - 14 Oct 1582 skipped "
			end	
			local setDate = fhNewDatePt(intYear,intMonth,intDayNo,datDate:GetYearDD())
			intDay = fhCallBuiltInFunction("DayNumber",setDate) 					-- Remove BC and Julian, Hebrew, French calendars
			if not intDay then intDay = 0 end
			local oldDate = fhNewDate()		oldDate:SetSimpleDate(datDate)		-- Report problem to user
			local newDate = fhNewDate()		newDate:SetSimpleDate(setDate)
			local strIsBC = ""
			if datDate:GetBC() then
				strError = strError.." B.C. disallowed "
				intDay = -intDay
				strIsBC = "and Day Number negated"
			end
			fhMessageBox("\n Get Day Number issue for date \n "..oldDate:GetDisplayText().." \n "..strError.." \n So replaced it with date \n "..newDate:GetDisplayText().." \n "..strIsBC)
		end
		return intDay
	end -- function GetDayNumber

	local dtpYearMin = fhNewDatePt(1000)												-- Minimum year to use when earliest estimate is null
	local dtpYearMax = fhNewDatePt(2000)												-- Maximum year to use when latest estimate is null

	function fh.GetYearToday()																-- Get the Year for Today
		local intYearToday = fhCallBuiltInFunction("Year",fhCallBuiltInFunction("Today"))
		dtpYearMax = fhNewDatePt(intYearToday)											-- Set maximum year date point
		return intYearToday
	end -- function GetYearToday()

	local function getDeathFacts(ptrIndi)												-- Iterate Death, Burial, Cremation facts
		local arrFact = { "~.DEAT"; "~.BURI"; "~.CREM"; }
		local intFact = 0
		local ptrFact = fhNewItemPtr()													-- Pointer to each fact returned to user
		return function ()
			while intFact < #arrFact do
				intFact = intFact + 1
				ptrFact = fhGetItemPtr(ptrIndi,arrFact[intFact])
				if ptrFact:IsNotNull() then return ptrFact end
			end
		end
	end -- local function getDeathFacts

	-- Ensure Estimated Date EARLIEST <= LATEST <= Fact Date --					-- Fix errors in EstimatedBirth/DeathDate function
	local function estimatedDates(strFunc,ptrIndi,intGens,getFact,intYrs)
		-- strFunc	"EstimatedBirthDate" or "EstimatedDeathDate"
		-- ptrIndi	Individual of interest
		-- intGens	Number of generations (may be nil)
		-- getFact	Iterator function for facts
		-- intYrs 	Years to add to After dates
		intGens = intGens or 2
		local dtpMin = fhCallBuiltInFunction(strFunc,ptrIndi,"EARLIEST",intGens)
		local dtpMax = fhCallBuiltInFunction(strFunc,ptrIndi,"LATEST",intGens)
		local dtpMid = fhNewDatePt()
		if not ( dtpMin:IsNull() and dtpMax:IsNull() ) then 						-- Skip if both null	
			if dtpMax:IsNull() then dtpMax = dtpYearMax elseif dtpMin:IsNull() then dtpMin = dtpYearMin end
			for ptrFact in getFact(ptrIndi) do
				local datFact = fhGetValueAsDate(fhGetItemPtr(ptrFact,"~.DATE"))
				if not datFact:IsNull() then												-- Find 1st Fact Date
					local dtpLast = datFact:GetDatePt1()								-- Last date = DatePt1 for Simple, Range, and Before
					local strType = datFact:GetSubtype()								-- Between = DatePt2 and After = DatePt1 + intYrs
					if   strType == "Between" then dtpLast = datFact:GetDatePt2()
					elseif strType == "After" then dtpLast = fhNewDatePt(dtpLast:GetYear()+intYrs,dtpLast:GetMonth(),dtpLast:GetDay()) end		-- Compare only uses Year, Month, Day so omitted	,dtpLast:GetYearDD(),dtpLast:GetBC(),dtpLast:GetCalendar()
					if dtpMax:Compare(dtpLast) > 0 then dtpMax = dtpLast end
					if dtpMin:Compare(dtpMax)  > 0 then dtpMin = dtpMax  end
					if strType ~= "After" then break end								-- Now EARLIEST <= LATEST <= Last date
				end
			end	
			local intDays = ( fh.GetDayNumber(dtpMax) - fh.GetDayNumber(dtpMin) ) / 2
			local intYear,remYear = math.modf( intDays / 365.2422 )				-- Offset year @ 365.2422 days per year, and remainder fraction
			local intMnth = math.floor( ( remYear * 12 ) + 0.1 )					-- Offset month is remainder fraction of year * 12
			dtpMid = fhCallBuiltInFunction("CalcDate",dtpMin,intYear,intMnth)	-- Need approximate MID year & month
		end
		return { Min=dtpMin; Mid=dtpMid; Max=dtpMax; }								-- Return EARLIEST, MID, LATEST dates
	end -- local function estimatedDates

	-- Make EstimatedBirthDate EARLIEST <= LATEST <= 1st Fact Date --			-- Fix errors in EstimatedBirthDate function
	function fh.EstimatedBirthDates(ptrIndi,intGens)
		return estimatedDates("EstimatedBirthDate",ptrIndi,intGens,iterate.Facts,10)
	end -- function EstimatedBirthDates

	-- Make EstimatedDeathDate EARLIEST <= LATEST <= DEAT/BURI/CREM Date --	-- Fix errors in EstimatedDeathDate function
	function fh.EstimatedDeathDates(ptrIndi,intGens)
		return estimatedDates("EstimatedDeathDate",ptrIndi,intGens,getDeathFacts,100)
	end -- function EstimatedDeathDates

	--[[
	@function:		BuildDataRef
	@description:	Get Full Data Reference for Pointer
	@parameters:		Item Pointer
	@returns:			Data Reference String, Record Id Integer, Record Type Tag String
	@requires:		None
	]]
	function fh.BuildDataRef(ptrRef)

		local strDataRef = ""										-- Data Reference with instance indices e.g. INDI.RESI[2].ADDR
		local intRecId   = 0										-- Record Id for associated Record
		local strRecTag  = ""										-- Record Tag of associated Record type i.e. INDI, FAM, NOTE, SOUR, etc

		-- getDataRef() is called recursively per level of the Data Ref
		-- ptrRef points to the upper Data Ref levels yet to be analysed
		-- strRef compiles the lower Data Ref levels including instances

		local function getDataRef(ptrRef,strRef)
			local ptrTag = ptrRef:Clone()
			local strTag = fhGetTag(ptrTag)						-- Current level Tag
			ptrTag:MoveToParentItem(ptrTag)
			if ptrTag:IsNotNull() then							-- Parent level exists
				local intSib = 1
				local ptrSib = ptrRef:Clone()					-- Pointer to siblings with same Tag
				ptrSib:MovePrev("SAME_TAG")
				while ptrSib:IsNotNull() do						-- Count previous siblings with same Tag
					intSib = intSib + 1
					ptrSib:MovePrev("SAME_TAG")
				end
				if intSib > 1 then 	strTag = strTag.."["..intSib.."]" end
				getDataRef(ptrTag,"."..strTag..strRef)			-- Now analyse the parent level
			else
				strDataRef = strTag..strRef						-- Record level reached, so set return values
				intRecId   = fhGetRecordId(ptrRef)
				strRecTag  = strTag
				if not fhIsValidDataRef(strDataRef) then print("BuildDataRef: "..strDataRef.." is Invalid") end
			end
		end -- local function getDataRef

		if type(ptrRef) == "userdata" then getDataRef(ptrRef,"") end

		return strDataRef, intRecId, strRecTag

	end -- function BuildDataRef

	--[[
	@function:		GetDataRefPtr
	@description:	Get Pointer for Full Data Reference
	@parameters:		Data Reference String, Record Id Integer, Record Type Tag String (optional)
	@returns:			Item Pointer which IsNull() if any parameters are invalid
	@requires:		None
	]]
	function fh.GetDataRefPtr(strDataRef,intRecId,strRecTag)
		strDataRef = strDataRef or ""
		if not strRecTag then
			strRecTag = strDataRef:gsub("^(%u+).*$","%1")	-- Extract Record Tag from Data Ref
		end
		local ptrRef = fhNewItemPtr()
		ptrRef:MoveToRecordById(strRecTag,intRecId or 0)	-- Lookup the Record by Id
		ptrRef:MoveTo(ptrRef,strDataRef)						-- Move to the Data Ref
		return ptrRef
	end -- function GetDataRefPtr

	function fh.TblDataRef(ptrRef)
		local tblRef = {}
		tblRef.DataRef, tblRef.RecId, tblRef.RecTag = BuildDataRef(ptrRef)
		return tblRef
	end -- function TblDataRef

	function fh.PtrDataRef(tblRef)
		local tblRef = tblRef or {}								-- Ensure table and its fields exist
		return GetDataRefPtr(tblRef.DataRef or "",tblRef.RecId or 0,tblRef.RecTag or "")
	end -- function PtrDataRef

	return fh

end -- local function general_v3

local general = general_v3()										-- To access FH general tools module

--[[
@Module:			+fh+encoder_v3
@Author:			Mike Tate
@Version:			3.5
@LastUpdated:	25 Aug 2020
@Description:	Text encoder module for HTML XHTML XML URI UTF8 UTF16 ISO CP1252/ANSI character codings.
@V3.5:				Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility.
@V3.4:				Ensure expressions involving gsub return just text parameter.
@V3.3:				Adds UNICODE U+10000 to U+10FFFF UTF-16 Supplementary Planes.
@V3.2:				Update for ANSI & Unicode to ASCII for sorting, Soundex, etc.
@V3.1:				Update for Unicode UTF-16 & UTF-8 and fhConvertANSItoUTF8 & fhConvertUTF8toANSI, name change UTF to UTF8 & CP to ANSI.
@V2.0:				StrUTF8_Encode() replaced by StrUTF_CP1252() for entire UTF-8 range, plus new StrCP1252_ISO().
@V1.0:				Initial version.
]]

local function encoder_v3()

	local fh = {}													-- Local environment table

	local fhVersion = fhGetAppVersion()

	local br_Tag = "
" -- Markup language break tag default local br_Lua = "
" -- Lua pattern for break tag recognition local tblCodePage = {} -- Code Page to XML/XHTML/HTML/URI/UTF8 encodings: http://en.wikipedia.org/wiki/Windows-1252 & 1250 & etc -- Control characters "\000" to "\031" for URI & Markup "[%c]" encodings are disallowed except for "\t" to "\r" tblCodePage["\000"] = "" -- NUL tblCodePage["\001"] = "" -- SOH tblCodePage["\002"] = "" -- STX tblCodePage["\003"] = "" -- ETX tblCodePage["\004"] = "" -- EOT tblCodePage["\005"] = "" -- ENQ tblCodePage["\006"] = "" -- ACK tblCodePage["\a"] = "" -- BEL tblCodePage["\b"] = "" -- BS tblCodePage["\t"] = "+" -- HT space in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["\n"] = "%0A" -- LF br_Tag in Markup tblCodePage["\v"] = "%0A" -- VT br_Tag in Markup tblCodePage["\f"] = "%0A" -- FF br_Tag in Markup tblCodePage["\r"] = "%0D" -- CR br_Tag in Markup tblCodePage["\014"] = "" -- SO tblCodePage["\015"] = "" -- SI tblCodePage["\016"] = "" -- DLE tblCodePage["\017"] = "" -- DC1 tblCodePage["\018"] = "" -- DC2 tblCodePage["\019"] = "" -- DC3 tblCodePage["\020"] = "" -- DC4 tblCodePage["\021"] = "" -- NAK tblCodePage["\022"] = "" -- SYN tblCodePage["\023"] = "" -- ETB tblCodePage["\024"] = "" -- CAN tblCodePage["\025"] = "" -- EM tblCodePage["\026"] = "" -- SUB tblCodePage["\027"] = "" -- ESC tblCodePage["\028"] = "" -- FS tblCodePage["\029"] = "" -- GS tblCodePage["\030"] = "" -- RS tblCodePage["\031"] = "" -- US -- ASCII characters "\032" to "\127" for URI "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding tblCodePage[" "] = "+" -- or "%20" Space tblCodePage["!"] = "%21" -- Reserved character tblCodePage['"'] = "%22" -- """ in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["#"] = "%23" -- Reserved character tblCodePage["$"] = "%24" -- Reserved character tblCodePage["%"] = "%25" -- Must be encoded tblCodePage["&"] = "%26" -- Reserved character -- "&" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["'"] = "%27" -- Reserved character -- "'" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["("] = "%28" -- Reserved character tblCodePage[")"] = "%29" -- Reserved character tblCodePage["*"] = "%2A" -- Reserved character tblCodePage["+"] = "%2B" -- Reserved character tblCodePage[","] = "%2C" -- Reserved character -- tblCodePage["-"] = "%2D" -- Unreserved character not encoded -- tblCodePage["."] = "%2E" -- Unreserved character not encoded tblCodePage["/"] = "%2F" -- Reserved character -- Digits 0 to 9 -- Unreserved characters not encoded tblCodePage[":"] = "%3A" -- Reserved character tblCodePage[";"] = "%3B" -- Reserved character tblCodePage["<"] = "%3C" -- "<" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["="] = "%3D" -- Reserved character tblCodePage[">"] = "%3E" -- ">" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["?"] = "%3F" -- Reserved character tblCodePage["@"] = "%40" -- Reserved character -- Letters A to Z -- Unreserved characters not encoded tblCodePage["["] = "%5B" -- Reserved character tblCodePage["\\"]= "%5C" tblCodePage["]"] = "%5D" -- Reserved character tblCodePage["^"] = "%5E" -- tblCodePage["_"] = "%5F" -- Unreserved character not encoded tblCodePage["`"] = "%60" -- Letters a to z -- Unreserved characters not encoded tblCodePage["{"] = "%7B" tblCodePage["|"] = "%7C" tblCodePage["}"] = "%7D" -- tblCodePage["~"] = "%7E" -- Unreserved character not encoded tblCodePage["\127"] = "" -- DEL -- Code Page 1252 Unicode characters "\128" to "\255" for UTF-8 scheme "[-]" encodings: http://en.wikipedia.org/wiki/UTF-8 tblCodePage[""] = string.char(0xE2,0x82,0xAC) -- "€" tblCodePage["\129"] = "" -- Undefined tblCodePage[""] = string.char(0xE2,0x80,0x9A) tblCodePage[""] = string.char(0xC6,0x92) tblCodePage[""] = string.char(0xE2,0x80,0x9E) tblCodePage[""] = string.char(0xE2,0x80,0xA6) tblCodePage[""] = string.char(0xE2,0x80,0xA0) tblCodePage[""] = string.char(0xE2,0x80,0xA1) tblCodePage[""] = string.char(0xCB,0x86) tblCodePage[""] = string.char(0xE2,0x80,0xB0) tblCodePage[""] = string.char(0xC5,0xA0) tblCodePage[""] = string.char(0xE2,0x80,0xB9) tblCodePage[""] = string.char(0xC5,0x92) tblCodePage["\141"] = "" -- Undefined tblCodePage[""] = string.char(0xC5,0xBD) tblCodePage["\143"] = "" -- Undefined tblCodePage["\144"] = "" -- Undefined tblCodePage[""] = string.char(0xE2,0x80,0x98) tblCodePage[""] = string.char(0xE2,0x80,0x99) tblCodePage[""] = string.char(0xE2,0x80,0x9C) tblCodePage[""] = string.char(0xE2,0x80,0x9D) tblCodePage[""] = string.char(0xE2,0x80,0xA2) tblCodePage[""] = string.char(0xE2,0x80,0x93) tblCodePage[""] = string.char(0xE2,0x80,0x94) tblCodePage["\152"] = string.char(0xCB,0x9C) -- Small Tilde tblCodePage[""] = string.char(0xE2,0x84,0xA2) tblCodePage[""] = string.char(0xC5,0xA1) tblCodePage[""] = string.char(0xE2,0x80,0xBA) tblCodePage[""] = string.char(0xC5,0x93) tblCodePage["\157"] = "" -- Undefined tblCodePage[""] = string.char(0xC5,0xBE) tblCodePage[""] = string.char(0xC5,0xB8) tblCodePage["\160"] = string.char(0xC2,0xA0) -- " " No Break Space tblCodePage[""] = string.char(0xC2,0xA1) -- "¡" tblCodePage[""] = string.char(0xC2,0xA2) -- "¢" tblCodePage[""] = string.char(0xC2,0xA3) -- "£" tblCodePage[""] = string.char(0xC2,0xA4) -- "¤" tblCodePage[""] = string.char(0xC2,0xA5) -- "¥" tblCodePage[""] = string.char(0xC2,0xA6) tblCodePage[""] = string.char(0xC2,0xA7) tblCodePage[""] = string.char(0xC2,0xA8) tblCodePage[""] = string.char(0xC2,0xA9) tblCodePage[""] = string.char(0xC2,0xAA) tblCodePage[""] = string.char(0xC2,0xAB) tblCodePage[""] = string.char(0xC2,0xAC) tblCodePage[""] = string.char(0xC2,0xAD) -- "­" Soft Hyphen tblCodePage[""] = string.char(0xC2,0xAE) tblCodePage[""] = string.char(0xC2,0xAF) tblCodePage[""] = string.char(0xC2,0xB0) tblCodePage[""] = string.char(0xC2,0xB1) tblCodePage[""] = string.char(0xC2,0xB2) tblCodePage[""] = string.char(0xC2,0xB3) tblCodePage[""] = string.char(0xC2,0xB4) tblCodePage[""] = string.char(0xC2,0xB5) tblCodePage[""] = string.char(0xC2,0xB6) tblCodePage[""] = string.char(0xC2,0xB7) tblCodePage[""] = string.char(0xC2,0xB8) tblCodePage[""] = string.char(0xC2,0xB9) tblCodePage[""] = string.char(0xC2,0xBA) tblCodePage[""] = string.char(0xC2,0xBB) tblCodePage[""] = string.char(0xC2,0xBC) tblCodePage[""] = string.char(0xC2,0xBD) tblCodePage[""] = string.char(0xC2,0xBE) tblCodePage[""] = string.char(0xC2,0xBF) tblCodePage[""] = string.char(0xC3,0x80) tblCodePage[""] = string.char(0xC3,0x81) tblCodePage[""] = string.char(0xC3,0x82) tblCodePage[""] = string.char(0xC3,0x83) tblCodePage[""] = string.char(0xC3,0x84) tblCodePage[""] = string.char(0xC3,0x85) tblCodePage[""] = string.char(0xC3,0x86) tblCodePage[""] = string.char(0xC3,0x87) tblCodePage[""] = string.char(0xC3,0x88) tblCodePage[""] = string.char(0xC3,0x89) tblCodePage[""] = string.char(0xC3,0x8A) tblCodePage[""] = string.char(0xC3,0x8B) tblCodePage[""] = string.char(0xC3,0x8C) tblCodePage[""] = string.char(0xC3,0x8D) tblCodePage[""] = string.char(0xC3,0x8E) tblCodePage[""] = string.char(0xC3,0x8F) tblCodePage[""] = string.char(0xC3,0x90) tblCodePage[""] = string.char(0xC3,0x91) tblCodePage[""] = string.char(0xC3,0x92) tblCodePage[""] = string.char(0xC3,0x93) tblCodePage[""] = string.char(0xC3,0x94) tblCodePage[""] = string.char(0xC3,0x95) tblCodePage[""] = string.char(0xC3,0x96) tblCodePage[""] = string.char(0xC3,0x97) tblCodePage[""] = string.char(0xC3,0x98) tblCodePage[""] = string.char(0xC3,0x99) tblCodePage[""] = string.char(0xC3,0x9A) tblCodePage[""] = string.char(0xC3,0x9B) tblCodePage[""] = string.char(0xC3,0x9C) tblCodePage[""] = string.char(0xC3,0x9D) tblCodePage[""] = string.char(0xC3,0x9E) tblCodePage[""] = string.char(0xC3,0x9F) tblCodePage[""] = string.char(0xC3,0xA0) tblCodePage[""] = string.char(0xC3,0xA1) tblCodePage[""] = string.char(0xC3,0xA2) tblCodePage[""] = string.char(0xC3,0xA3) tblCodePage[""] = string.char(0xC3,0xA4) tblCodePage[""] = string.char(0xC3,0xA5) tblCodePage[""] = string.char(0xC3,0xA6) tblCodePage[""] = string.char(0xC3,0xA7) tblCodePage[""] = string.char(0xC3,0xA8) tblCodePage[""] = string.char(0xC3,0xA9) tblCodePage[""] = string.char(0xC3,0xAA) tblCodePage[""] = string.char(0xC3,0xAB) tblCodePage[""] = string.char(0xC3,0xAC) tblCodePage[""] = string.char(0xC3,0xAD) tblCodePage[""] = string.char(0xC3,0xAE) tblCodePage[""] = string.char(0xC3,0xAF) tblCodePage[""] = string.char(0xC3,0xB0) tblCodePage[""] = string.char(0xC3,0xB1) tblCodePage[""] = string.char(0xC3,0xB2) tblCodePage[""] = string.char(0xC3,0xB3) tblCodePage[""] = string.char(0xC3,0xB4) tblCodePage[""] = string.char(0xC3,0xB5) tblCodePage[""] = string.char(0xC3,0xB6) tblCodePage[""] = string.char(0xC3,0xB7) tblCodePage[""] = string.char(0xC3,0xB8) tblCodePage[""] = string.char(0xC3,0xB9) tblCodePage[""] = string.char(0xC3,0xBA) tblCodePage[""] = string.char(0xC3,0xBB) tblCodePage[""] = string.char(0xC3,0xBC) tblCodePage[""] = string.char(0xC3,0xBD) tblCodePage[""] = string.char(0xC3,0xBE) tblCodePage[""] = string.char(0xC3,0xBF) -- Set XML/XHTML/HTML "[%c\"&'<>]" Markup encodings: http://en.wikipedia.org/wiki/XML and http://en.wikipedia.org/wiki/HTML local function setMarkupEncodings() tblCodePage["\t"] = " " -- HT "\t" to "\r" are treated as white space in Markup Languages by default tblCodePage["\n"] = br_Tag -- LF tblCodePage["\v"] = br_Tag -- VT line break tag "
" or "
" or "
" or "
" is better tblCodePage["\f"] = br_Tag -- FF tblCodePage["\r"] = br_Tag -- CR tblCodePage['"'] = """ tblCodePage["&"] = "&" tblCodePage["'"] = "'" tblCodePage["<"] = "<" tblCodePage[">"] = ">" end -- local function setMarkupEncodings -- Set URI/URL/URN "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding local function setURIEncodings() tblCodePage["\t"] = "+" -- HT space tblCodePage["\n"] = "%0A" -- LF newline tblCodePage["\v"] = "%0A" -- VT newline tblCodePage["\f"] = "%0A" -- FF newline tblCodePage["\r"] = "%0D" -- CR return tblCodePage['"'] = "%22" tblCodePage["&"] = "%26" tblCodePage["'"] = "%27" tblCodePage["<"] = "%3C" tblCodePage[">"] = "%3E" end -- local function setURIEncodings -- Encode characters according to gsub pattern & lookup table -- local function strEncode(strText,strPattern,tblPattern) return ( (strText or ""):gsub(strPattern,tblPattern) ) -- V3.4 end -- local function strEncode -- Encode CP1252/ANSI characters into UTF-8 codes -- function fh.StrANSI_UTF8(strText) if fhVersion > 5 then strText = fhConvertANSItoUTF8(strText) else strText = strEncode(strText,"[\127-]",tblCodePage) end return strText end -- function StrANSI_UTF8 function fh.StrCP_UTF(strText) -- Legacy return fh.StrANSI_UTF8(strText) end -- function StrCP1252_UTF8 function fh.StrCP1252_UTF(strText) -- Legacy return fh.StrANSI_UTF8(strText) end -- function StrCP1252_UTF -- Encode CP1252/ANSI or UTF-8 characters into UTF-8 -- function fh.StrEncode_UTF8(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_UTF8(strText) else return strText end end -- function StrEncode_UTF8 -- Encode CP1252/ANSI characters into XML/XHTML/HTML/UTF8 codes -- local strANSI_XML = "[%z\001-\031\"&'<>\127-]" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strANSI_XML = "[\000-\031\"&'<>\127-]" end function fh.StrANSI_XML(strText) setMarkupEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag strText = strEncode(strText,strANSI_XML,tblCodePage) return strText end -- function StrANSI_XML function StrCP_XML(strText) -- Legacy return fh.StrANSI_XML(strText) end -- function StrCP_XML function StrCP1252_XML(strText) -- Legacy return fh.StrANSI_XML(strText) end -- function StrCP1252_XML -- Encode UTF-8 ASCII characters into XML/XHTML/HTML codes -- local strUTF8_XML = "[%z\001-\031\"&'<>\127]" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strUTF8_XML = "[\000-\031\"&'<>\127]" end function fh.StrUTF8_XML(strText) setMarkupEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag strText = strEncode(strText,strUTF8_XML,tblCodePage) return strText end -- function StrUTF8_XML -- Encode CP1252/ANSI or UTF-8 ASCII characters into XML/XHTML/HTML codes -- function fh.StrEncode_XML(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_XML(strText) else return fh.StrUTF8_XML(strText) end end -- function StrEncode_XML -- Encode Item Text characters into XML/HTML/UTF-8 codes -- function fh.StrGetItem_XML(ptrItem,strTags) return fh.StrEncode_XML(fhGetItemText(ptrItem,strTags)) end -- function StrGetItem_XML -- Encode CP1252/ANSI characters into URI codes -- function fh.StrANSI_URI(strText) setURIEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes %0A strText = strEncode(strText,"[^0-9A-Za-z]",tblCodePage) return strText end -- function StrANSI_URI function fh.StrCP_URI(strText) return fh.StrANSI_URI(strText) end -- function StrCP_URI function fh.StrCP1252_URI(strText) return fh.StrANSI_URI(strText) end -- function StrCP1252_URI -- Encode UTF-8 ASCII characters into URI codes -- local strUTF8_URI = "[%z\001-\127]" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strUTF8_URI = "[\000-\127]" end function fh.StrUTF8_URI(strText) setURIEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag strText = strEncode(strText,strUTF8_URI,tblCodePage) return strText end -- function StrUTF8_URI -- Encode CP1252/ANSI or UTF-8 ASCII characters into URI codes -- function fh.StrEncode_URI(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_URI(strText) else return fh.StrUTF8_URI(strText) end end -- function StrEncode_URI function fh.StrUTF8_Encode(strText) -- Legacy from V1.0 return fh.StrUTF8_ANSI(strText) end -- function StrUTF8_Encode -- Encode UTF-8 bytes into single CP1252/ANSI character V2.0 upvalues -- local strByteRange = "["..string.char(0xC0).."-"..string.char(0xFF).."]" local tblBytePoint = {0xC0;0xE0;0xF0;0xF8;0xFC;} -- Byte codes for 2-byte, 3-byte, 4-byte, 5-byte, 6-byte UTF-8 local tblUTF8 = {} for strByte = string.byte(""), string.byte("") do local strChar = string.char(strByte) -- Use CodePage to UTF-8 table to populate UTF-8 to CodePage table local strCode = tblCodePage[strChar] tblUTF8[strCode] = strChar end -- Encode UTF-8 bytes into single CP1252/ANSI character -- function fh.StrUTF8_ANSI(strText) strText = strText or "" if fhVersion > 5 then return fhConvertUTF8toANSI(strText) end if strText:match(strByteRange) then -- If text contains characters that need translating then local intChar = 0 -- Input character index local strChar = "" -- Current character local strCode = "" -- UTF-8 multi-byte code local tblLine = {} -- Translated output line repeat intChar = intChar + 1 -- Step through each character in text strChar = strText:sub(intChar,intChar) if strChar:match(strByteRange) then -- Convert UTF-8 bytes into CP character strCode = strChar -- First UTF-8 byte code, whose top bits say how many bytes to append for intByte, strByte in ipairs(tblBytePoint) do if string.byte(strChar) >= strByte then intChar = intChar + 1 -- Append next UTF-8 byte code character strCode = strCode..strText:sub(intChar,intChar) else break end end strChar = tblUTF8[strCode] or "" -- Translate UTF-8 code into CP character end table.insert(tblLine,strChar) -- Accumulate output char by char until intChar >= #strText strText = table.concat(tblLine) end return strText end -- function StrUTF8_ANSI function fh.StrUTF_CP(strText) -- Legacy return fh.StrUTF8_ANSI(strText) end -- function StrUTF_CP function fh.StrUTF_CP1252(strText) -- Legacy return fh.StrUTF8_ANSI(strText) end -- function StrUTF_CP1252 -- Encode CP1252/ANSI or UTF-8 characters into ANSI -- function fh.StrEncode_ANSI(strText) if stringx.encoding() == "ANSI" then return strText or "" else return fh.StrUTF8_ANSI(strText) end end -- function StrEncode_ANSI -- Set ISO-8859-1 "[\127-]" encodings: http://en.wikipedia.org/wiki/ISO/IEC_8859-1 local tblISO8859 = { } tblISO8859["\127"]="" -- DEL tblISO8859[""] = "EUR" tblISO8859["\129"]="" -- Undefined tblISO8859[""] = "" tblISO8859[""] = "f" tblISO8859[""] = "" tblISO8859[""] = "..." tblISO8859[""] = "+" tblISO8859[""] = "" tblISO8859[""] = "^" tblISO8859[""] = "%" tblISO8859[""] = "S" tblISO8859[""] = "<" tblISO8859[""] = "OE" tblISO8859["\141"]="" -- Undefined tblISO8859[""] = "Z" tblISO8859["\143"]="" -- Undefined tblISO8859["\144"]="" -- Undefined tblISO8859[""] = "'" tblISO8859[""] = "'" tblISO8859[""] = '"' tblISO8859[""] = '"' tblISO8859[""] = "" tblISO8859[""] = "-" tblISO8859[""] = "-" tblISO8859["\152"]="~" -- Small Tilde tblISO8859[""] = "TM" tblISO8859[""] = "s" tblISO8859[""] = ">" tblISO8859[""] = "oe" tblISO8859["\157"]="" -- Undefined tblISO8859[""] = "z" tblISO8859[""] = "Y" -- Encode CP1252/ANSI characters into ISO-8859-1 codes -- function fh.StrANSI_ISO(strText) return strEncode(strText,"[\127-]",tblISO8859) end -- function StrANSI_ISO function fh.StrCP_ISO(strText) -- Legacy return fh.StrANSI_ISO(strText) end -- function StrCP_ISO function fh.StrCP1252_ISO(strText) -- Legacy return fh.StrANSI_ISO(strText) end -- function StrCP1252_ISO function fh.StrUTF8_ISO(strText) return fh.StrANSI_ISO(fh.StrUTF8_ANSI(strText)) end -- function StrUTF8_ISO -- Encode CP1252/ANSI or UTF-8 ASCII characters into ISO-8859-1 codes -- function fh.StrEncode_ISO(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_ISO(strText) else return fh.StrUTF8_ISO(strText) end end -- function StrEncode_ISO -- Convert UTF-8 bytes to a UTF-16 word or pair -- local tblByte = {} local tblLead = { 0x80; 0xC0; 0xE0; 0xF0; 0xF8; 0xFC; } function fh.StrUtf8toUtf16(strChar) -- Convert any UTF-8 multibytes to UTF-16 -- local function strUtf8() if #tblByte > 0 then local intUtf16 = 0 for intIndex, intByte in ipairs (tblByte) do -- Convert UTF-8 bytes to UNICODE U+0080 to U+10FFFF if intIndex == 1 then intUtf16 = intByte - tblLead[#tblByte] else intUtf16 = intUtf16 * 0x40 + intByte - 0x80 end end if intUtf16 > 0xFFFF then -- U+10000 to U+10FFFF Supplementary Planes -- V2.6 tblByte = {} intUtf16 = intUtf16 - 0x10000 local intLow10 = 0xDC00 + ( intUtf16 % 0x400 ) -- Low 16-bit Surrogate local intTop10 = 0xD800 + math.floor( intUtf16 / 0x400 ) -- High 16-bit Surrogate local intChar1 = intTop10 % 0x100 local intChar2 = math.floor( intTop10 / 0x100 ) local intChar3 = intLow10 % 0x100 local intChar4 = math.floor( intLow10 / 0x100 ) return string.char(intChar1,intChar2,intChar3,intChar4) -- Surrogate 16-bit Pair end if intUtf16 < 0xD800 -- U+0080 to U+FFFF (except U+D800 to U+DFFF) -- V2.6 or intUtf16 > 0xDFFF then -- Basic Multilingual Plane tblByte = {} local intChar1 = intUtf16 % 0x100 local intChar2 = math.floor( intUtf16 / 0x100 ) return string.char(intChar1,intChar2) -- BPL 16-bit end local strUtf8 = "" -- U+D800 to U+DFFF Reserved Code Points -- V2.6 for intIndex, intByte in ipairs (tblByte) do strUtf8 = strUtf8..string.format("%.2X ",intByte) end local strUtf16 = string.format("%.4X ",intUtf16) fhMessageBox("\n UTF-16 Reserved Code Point U+D800 to U+DFFF \n UTF-16 = "..strUtf16.." UTF-8 = "..strUtf8.."\n Character will be replaced by a question mark. \n") tblByte = {} return "?\0" end return "" end -- local function strUtf8 local intUtf8 = string.byte(strChar) if intUtf8 < 0x80 then -- U+0000 to U+007F (ASCII) return strUtf8()..strChar.."\0" -- Previous UTF-8 multibytes + current ASCII char end if intUtf8 >= 0xC0 then -- Next UTF-8 multibyte start local strUtf16 = strUtf8() table.insert(tblByte,intUtf8) return strUtf16 -- Previous UTF-8 multibytes end table.insert(tblByte,intUtf8) return "" end -- function StrUtf8toUtf16 -- Encode UTF-8 bytes into UTF-16 words -- function fh.StrUTF8_UTF16(strText) tblByte = {} -- (0xFF) flushes last UTF-8 character return ( ((strText or "")..string.char(0xFF)):gsub("(.)",fh.StrUtf8toUtf16) ) -- V3.4 end -- function StrUTF8_UTF16 -- Encode CP1252/ANSI or UTF-8 characters into UTF-16 words -- function fh.StrEncode_UTF16(strText) if stringx.encoding() == "ANSI" then strText = fh.StrANSI_UTF8(strText) end return fh.StrUTF8_UTF16(strText) end -- function StrEncode_UTF16 local intTop10 = 0 -- Convert a UTF-16 word or pair to UTF-8 bytes -- function fh.StrUtf16toUtf8(strChar1,strChar2) local intUtf16 = string.byte(strChar2) * 0x100 + string.byte(strChar1) if intUtf16 < 0x80 then -- U+0000 to U+007F (ASCII) return string.char(intUtf16) end if intUtf16 < 0x800 then -- U+0080 to U+07FF local intByte1 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte2 = intUtf16 return string.char( intByte2 + 0xC0, intByte1 + 0x80 ) end if intUtf16 < 0xD800 -- U+0800 to U+FFFF or intUtf16 > 0xDFFF then local intByte1 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte2 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte3 = intUtf16 return string.char( intByte3 + 0xE0, intByte2 + 0x80, intByte1 + 0x80 ) end if intUtf16 < 0xDC00 then -- U+10000 to U+10FFFF High 16-bit Surrogate Supplementary Planes -- V2.6 intTop10 = ( intUtf16 - 0xD800 ) * 0x400 + 0x10000 return "" end intUtf16 = intUtf16 - 0xDC00 + intTop10 -- U+10000 to U+10FFFF Low 16-bit Surrogate Supplementary Planes -- V2.6 local intByte1 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte2 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte3 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte4 = intUtf16 return string.char( intByte4 + 0xF0, intByte3 + 0x80, intByte2 + 0x80, intByte1 + 0x80 ) end -- function StrUtf16toUtf8 -- Encode UTF-16 words into UTF-8 bytes -- function fh.StrUTF16_UTF8(strText) return ( (strText or ""):gsub("(.)(.)",fh.StrUtf16toUtf8) ) -- V3.4 end -- function StrUTF16_UTF8 -- Encode UTF-16 words into ANSI characters -- function fh.StrUTF16_ANSI(strText) return fh.StrUTF8_ANSI(fh.StrUTF16_UTF8(strText)) end -- function StrUTF16_ANSI -- Read UTF-16/UTF-8/ANSI file converted to chosen encoding via line iterator -- local strUtf16 = "^.%z" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strUtf16 = "^.\0" end function fh.FileLines(strFileName,strEncoding) -- Derived from http://lua-users.org/wiki/EnhancedFileLines local bomUtf16= "^"..string.char(0xFF,0xFE) -- "" local bomUtf8 = "^"..string.char(0xEF,0xBB,0xBF) -- "" local fncConv = tostring -- Function to convert input to current encoding local intHead = 1 -- Index to start of current text line local intLump = 1024 local fHandle = general.OpenFile(strFileName,"rb") local strText = fHandle:read(1024) -- Read first lump from file local intBOM = 0 strEncoding = strEncoding or string.encoding() if strText:match(bomUtf16) or strText:match(strUtf16) then strText,intBOM = strText:gsub(bomUtf16,"") -- Strip UTF-16 BOM if strEncoding == "ANSI" then -- Define UTF-16 conversion to current encoding fncConv = fh.StrUTF16_ANSI else fncConv = fh.StrUTF16_UTF8 end elseif strText:match(bomUtf8) then strText,intBOM = strText:gsub(bomUtf8,"") -- Strip UTF-8 BOM if strEncoding == "ANSI" then -- Define UTF-8 conversion to current encoding fncConv = fh.StrUTF8_ANSI end else if strEncoding == "UTF-8" then -- Define ANSI conversion to current encoding fncConv = fh.StrANSI_UTF8 end end strText = fncConv(strText) -- Convert first lump of text return function() -- Iterator function local intTail,strTail -- Index to end of current text line, and terminating characters while true do intTail, strTail = strText:match("()([\r\n].)",intHead) if intTail or not fHandle then if intHead > 1 then intLump = 0 end break -- End of line or end of file elseif fHandle then local strLump = fHandle:read(1024) -- Read next lump from file if strLump then -- Strip old text and add converted lump strText = strText:sub(intHead)..fncConv(strLump) intHead = 1 intLump = 1024 else assert(fHandle:close()) -- End of file fHandle = nil end end end if not intTail then intTail = #strText -- Last fragment of file elseif strTail == "\r\n" then intTail = intTail + 1 -- Adjust tail for both \r & \n end local strLine = strText:sub(intHead,intTail) -- Extract line from text intHead = intTail + 1 if #strLine > 0 then -- Return pruned line, tail chars, lump bytes read local strBody, strTail = strLine:match("^(.-)([\r\n]+)$") return strBody, strTail, intLump end end end -- function FileLines -- Set "[-]" ASCII encodings same as Unidecode below local tblASCII = { } tblASCII[""] = "=E" tblASCII["\129"]="" -- Undefined tblASCII[""] = "," tblASCII[""] = "f" tblASCII[""] = ",," tblASCII[""] = "..." tblASCII[""] = "|+" tblASCII[""] = "|++" tblASCII[""] = "^" tblASCII[""] = "%0" tblASCII[""] = "S" tblASCII[""] = "<" tblASCII[""] = "OE" tblASCII["\141"]="" -- Undefined tblASCII[""] = "Z" tblASCII["\143"]="" -- Undefined tblASCII["\144"]="" -- Undefined tblASCII[""] = "'" tblASCII[""] = "'" tblASCII[""] = "\"" tblASCII[""] = "\"" tblASCII[""] = "*" tblASCII[""] = "-" tblASCII[""] = "--" tblASCII["\152"]="~" -- Small Tilde tblASCII[""] = "TM" tblASCII[""] = "s" tblASCII[""] = ">" tblASCII[""] = "oe" tblASCII["\157"]="" -- Undefined tblASCII[""] = "z" tblASCII[""] = "Y" tblASCII["\160"]=" " -- " " No Break Space tblASCII[""] = "!" -- "¡" tblASCII[""] = "=c" -- "¢" tblASCII[""] = "=L" -- "£" tblASCII[""] = "=$" -- "¤" tblASCII[""] = "=Y" -- "¥" tblASCII[""] = "|" tblASCII[""] = "=SS" tblASCII[""] = "\"" tblASCII[""] = "(C)" tblASCII[""] = "a" tblASCII[""] = "<<" tblASCII[""] = "-" tblASCII[""] = "-" -- "­" Soft Hyphen tblASCII[""] = "(R)" tblASCII[""] = "-" tblASCII[""] = "=o" tblASCII[""] = "+-" tblASCII[""] = "2" tblASCII[""] = "3" tblASCII[""] = "'" tblASCII[""] = "=u" tblASCII[""] = "=p" tblASCII[""] = "*" tblASCII[""] = "," tblASCII[""] = "1" tblASCII[""] = "o" tblASCII[""] = ">>" tblASCII[""] = "1/4" tblASCII[""] = "1/2" tblASCII[""] = "3/4" tblASCII[""] = "?" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "AE" tblASCII[""] = "C" tblASCII[""] = "E" tblASCII[""] = "E" tblASCII[""] = "E" tblASCII[""] = "E" tblASCII[""] = "I" tblASCII[""] = "I" tblASCII[""] = "I" tblASCII[""] = "I" tblASCII[""] = "D" tblASCII[""] = "N" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "*" tblASCII[""] = "O" tblASCII[""] = "U" tblASCII[""] = "U" tblASCII[""] = "U" tblASCII[""] = "U" tblASCII[""] = "Y" tblASCII[""] = "TH" tblASCII[""] = "ss" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "ae" tblASCII[""] = "c" tblASCII[""] = "e" tblASCII[""] = "e" tblASCII[""] = "e" tblASCII[""] = "e" tblASCII[""] = "i" tblASCII[""] = "i" tblASCII[""] = "i" tblASCII[""] = "i" tblASCII[""] = "d" tblASCII[""] = "n" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "/" tblASCII[""] = "o" tblASCII[""] = "u" tblASCII[""] = "u" tblASCII[""] = "u" tblASCII[""] = "u" tblASCII[""] = "y" tblASCII[""] = "th" tblASCII[""] = "y" -- Encode CP1252/ANSI characters into ASCII codes [\000-\127] -- function fh.StrANSI_ASCII(strText) return strEncode(strText,"[-]",tblASCII) end -- function StrANSI_ASCII --[=[ Unidecode converts each codepoint into a few ASCII characters. Lookup table indexed by codepoint [0x0000]-[0xFFFF] gives an ASCII string. i.e. strASCII = Unidecode[intByte2][intByte1] or "=?" allowing for partially populated table. See http://search.cpan.org/dist/Text-Unidecode/ and follow Browse to: See http://cpansearch.perl.org/src/SBURKE/Text-Unidecode-1.22/lib/Text/Unidecode/ where each x??.pm gives 256 ASCII conversions. Start with the first few European accented characters, and add the others later. --]=] local Unidecode = { } function fh.StrUnidecode(strChar1,strChar2) -- Decode UTF-16 byte pair into ASCII characters return Unidecode[string.byte(strChar2)][string.byte(strChar1)] or "=?" end -- function StrUnidecode -- Encode UTF-8 characters into ASCII codes [\000-\126] -- function fh.StrUTF8_ASCII(strText) strText = fh.StrUTF8_UTF16(strText) -- Convert to UTF-16 Unicode and then to ASCII return ( strText:gsub("(.)(.)",fh.StrUnidecode) ) end -- function StrUTF8_ASCII -- Encode CP1252/ANSI or UTF-8 into ASCII codes [\000-\126] -- function fh.StrEncode_ASCII(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_ASCII(strText) else return fh.StrUTF8_ASCII(strText) end end -- function StrEncode_ASCII -- Set markup language break tag -- function fh.SetBreakTag(br_New) if not (br_New or ""):match(br_Lua) then -- Ensure new break tag is "
" or "
" or "
" or "
" br_New = "
" end br_Tag = br_New end -- function SetBreakTag for intByte = 0x00, 0xFF do Unidecode[intByte] = { } end Unidecode[0x00] = {[0]="\00";"\01";"\02";"\03";"\04";"\05";"\06";"\a";"\b";"\t";"\n";"\v";"\f";"\r";"\14";"\15";"\16";"\17";"\18";"\19";"\20";"\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\30";"\31"; " ";"!";'"';"#";"$";"%";"&";"'";"(";")";"*";"+";",";"-";".";"/";"0";"1";"2";"3";"4";"5";"6";"7";"8";"9";":";";";"<";"=";">";"?"; -- 0x20 to 0x3F "@";"A";"B";"C";"D";"E";"F";"G";"H";"I";"J";"K";"L";"M";"N";"O";"P";"Q";"R";"S";"T";"U";"V";"W";"X";"Y";"Z";"[";"\\";"]";"^";"_"; -- 0x40 to 0x5F "`";"a";"b";"c";"d";"e";"f";"g";"h";"i";"j";"k";"l";"m";"n";"o";"p";"q";"r";"s";"t";"u";"v";"w";"x";"y";"z";"{";"|";"}";"~";"\127"; -- 0x60 to 0x7F ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; -- 0x80 to 0x9F " ";"!";"=c";"=L";"=$";"=Y";"|";"=SS";'"';"(C)";"a";"<<";"-";"-";"(R)";"-";"=o";"+-";"2";"3";"'";"=u";"=P";"*";",";"1";"o";">>";"1/4";"1/2";"3/4";"?"; -- 0xA0 to 0xBF "A";"A";"A";"A";"A";"A";"AE";"C";"E";"E";"E";"E";"I";"I";"I";"I";"D";"N";"O";"O";"O";"O";"O";"*";"O";"U";"U";"U";"U";"Y";"TH";"ss"; -- 0xC0 to 0xDF "a";"a";"a";"a";"a";"a";"ae";"c";"e";"e";"e";"e";"i";"i";"i";"i";"d";"n";"o";"o";"o";"o";"o";"/";"o";"u";"u";"u";"u";"y";"th";"y"; -- 0xE0 to 0xFF } Unidecode[0x01] = {[0]="A";"a";"A";"a";"A";"a";"C";"c";"C";"c";"C";"c";"C";"c";"D";"d";"D";"d";"E";"e";"E";"e";"E";"e";"E";"e";"E";"e";"G";"g";"G";"g"; -- 0x00 to 0x1F "G";"g";"G";"g";"H";"h";"H";"h";"I";"i";"I";"i";"I";"i";"I";"i";"I";"i";"IJ";"ij";"J";"j";"K";"k";"k";"L";"l";"L";"l";"L";"l";"L"; -- 0x20 to 0x3F "l";"L";"l";"N";"n";"N";"n";"N";"n";"'n";"ng";"NG";"O";"o";"O";"o";"O";"o";"OE";"oe";"R";"r";"R";"r";"R";"r";"S";"s";"S";"s";"S";"s"; -- 0x40 to 0x5F "S";"s";"T";"t";"T";"t";"T";"t";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"W";"w";"Y";"y";"Y";"Z";"z";"Z";"z";"Z";"z";"s"; -- 0x60 to 0x7F "b";"B";"B";"b";"6";"6";"O";"C";"c";"D";"D";"D";"d";"d";"3";"@";"E";"F";"f";"G";"G";"hv";"I";"I";"K";"k";"l";"l";"W";"N";"n";"O"; -- 0x80 to 0x9F "O";"o";"OI";"oi";"P";"p";"YR";"2";"2";"SH";"sh";"t";"T";"t";"T";"U";"u";"Y";"V";"Y";"y";"Z";"z";"ZH";"ZH";"zh";"zh";"2";"5";"5";"ts";"w"; -- 0xA0 to 0xBF "|";"||";"|=";"!";"DZ";"Dz";"dz";"LJ";"Lj";"lj";"NJ";"Nj";"nj";"A";"a";"I";"i";"O";"o";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"@";"A";"a"; -- 0xC0 to 0xDF "A";"a";"AE";"ae";"G";"g";"G";"g";"K";"k";"O";"o";"O";"o";"ZH";"zh";"j";"DZ";"Dz";"dz";"G";"g";"HV";"W";"N";"n";"A";"a";"AE";"ae";"O";"o"; -- 0xE0 to 0xFF } Unidecode[0x02] = {[0]="A";"a";"A";"a";"E";"e";"E";"e";"I";"i";"I";"i";"O";"o";"O";"o";"R";"r";"R";"r";"U";"u";"U";"u";"S";"s";"T";"t";"Y";"y";"H";"h"; -- 0x00 to 0x1F "N";"d";"OU";"ou";"Z";"z";"A";"a";"E";"e";"O";"o";"O";"o";"O";"o";"O";"o";"Y";"y";"l";"n";"t";"j";"db";"qp";"A";"C";"c";"L";"T";"s"; -- 0x20 to 0x3F "z";"[?]";"[?]";"B";"U";"^";"E";"e";"J";"j";"q";"q";"R";"r";"Y";"y";"a";"a";"a";"b";"o";"c";"d";"d";"e";"@";"@";"e";"e";"e";"e";"j"; -- 0x40 to 0x5F "g";"g";"g";"g";"u";"Y";"h";"h";"i";"i";"I";"l";"l";"l";"lZ";"W";"W";"m";"n";"n";"n";"o";"OE";"O";"F";"r";"r";"r";"r";"r";"r";"r"; -- 0x60 to 0x7F "R";"R";"s";"S";"j";"S";"S";"t";"t";"u";"U";"v";"^";"w";"y";"Y";"z";"z";"Z";"Z";"?";"?";"?";"C";"@";"B";"E";"G";"H";"j";"k";"L"; -- 0x80 to 0x9F "q";"?";"?";"dz";"dZ";"dz";"ts";"tS";"tC";"fN";"ls";"lz";"WW";"]]";"h";"h";"h";"h";"j";"r";"r";"r";"r";"w";"y";"'";'"';"`";"'";"`";"`";"'"; -- 0xA0 to 0xBF "?";"?";"<";">";"^";"V";"^";"V";"'";"-";"/";"\\";",";"_";"\\";"/";":";".";"`";"'";"^";"V";"+";"-";"V";".";"@";",";"~";'"';"R";"X"; -- 0xC0 to 0xDF "G";"l";"s";"x";"?";"";"";"";"";"";"";"";"V";"=";'"';"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF } Unidecode[0x03] = { } Unidecode[0x04] = { } Unidecode[0x20] = {[0]=" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";"";"";"";"";"-";"-";"-";"-";"--";"--";"||";"_";"'";"'";",";"'";'"';'"';",,";'"'; -- 0x00 to 0x1F "|+";"|++";"*";"*>";".";"..";"...";".";"\n";"\n\n";"";"";"";"";"";" ";"%0";"%00";"'";"''";"'''";"`";"``";"```";"^";"<";">";"*";"!!";"!?";"-";"_"; -- 0x20 to 0x3F "-";"^";"***";"--";"/";"-[";"]-";"[?]";"?!";"!?";"7";"PP";"(]";"[)";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x40 to 0x5F "[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"0";"";"";"";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"n"; -- 0x60 to 0x7F "0";"1";"2";"3";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x80 to 0x9F "ECU";"CL";"Cr";"FF";"L";"mil";"N";"Pts";"Rs";"W";"NS";"D";"=E";"K";"T";"Dr";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xA0 to 0xBF "[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";""; -- 0xC0 to 0xDF "";"";"";"";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF } Unidecode[0x21] = {[34]="TM"; } return fh end -- local function encoder_v3 local encoder = encoder_v3() -- To access FH encoder chars module --[[ @Module: +fh+progbar_v3 @Author: Mike Tate @Version: 3.0 @LastUpdated: 27 Aug 2020 @Description: Progress Bar library module. @V3.0: Function Prototype Closure version. @V1.0: Initial version. ]] local function progbar_v3() local fh = {} -- Local environment table require "iuplua" -- To access GUI window builder iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28 local tblBars = {} -- Table for optional external attributes local strBack = "255 255 255" -- Background colour default is white local strBody = "0 0 0" -- Body text colour default is black local strFont = nil -- Font dialogue default is current font local strStop = "255 0 0" -- Stop button colour default is red local intPosX = iup.CENTER -- Show window default position is central local intPosY = iup.CENTER local intMax, intVal, intPercent, intStart, intDelta, intScale, strClock, isBarStop local lblText, barGauge, lblDelta, btnStop, dlgGauge local function doFocus() -- Bring the Progress Bar window into Focus dlgGauge.BringFront="YES" -- If used too often, inhibits other windows scroll bars, etc end -- local function doFocus local function doUpdate() -- Update the Progress Gauge and the Delta % with clock barGauge.Value = intVal lblDelta.Title = string.format("%4d %% %s ",math.floor(intPercent),strClock) end -- local function doUpdate local function doReset() -- Reset all dialogue variables and Update display intVal = 0 -- Current value of Progress Bar intPercent= 0.01 -- Percentage of progress intStart = os.time() -- Start time of progress intDelta = 0 -- Delta time of progress intScale = math.ceil( intMax / 1000 ) -- Scale of percentage per second of progress (initial guess is corrected in Step function) strClock = "00 : 00 : 00" -- Clock delta time display isBarStop = false -- Stop button pressed signal doUpdate() doFocus() end -- local function doReset function fh.Start(strTitle,intMaximum) -- Create & start Progress Bar window if not dlgGauge then strTitle = strTitle or "" -- Dialogue and button title intMax = intMaximum or 100 -- Maximun range of Progress Bar, default is 100 local strSize = tostring( math.max( 100, string.len(" Stop "..strTitle) * 8 ) ).."x30" -- Adjust Stop button size to Title lblText = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Progress Message"; } barGauge = iup.progressbar { RasterSize="400x30"; Value=0; Max=intMax; Tip="Progress Bar"; } lblDelta = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Percentage and Elapsed Time"; } btnStop = iup.button { Title=" Stop "..strTitle; RasterSize=strSize; FgColor=strStop; Tip="Stop Progress Button"; action=function() isBarStop = true end; } -- Signal Stop button pressed return iup.CLOSE -- Often caused main GUI to close !!! dlgGauge = iup.dialog { Title=strTitle.." Progress "; Font=strFont; FgColor=strBody; Background=strBack; DialogFrame="YES"; -- Remove Windows minimize/maximize menu iup.vbox{ Alignment="ACENTER"; Gap="10"; Margin="10x10"; lblText; barGauge; lblDelta; btnStop; }; move_cb = function(self,x,y) tblBars.X = x tblBars.Y = y end; close_cb = btnStop.action; -- Windows Close button = Stop button } if type(tblBars.GUI) == "table" and type(tblBars.GUI.ShowDialogue) == "function" then dlgGauge.move_cb = nil -- Use GUI library to show & move window tblBars.GUI.ShowDialogue("Bars",dlgGauge,btnStop,"showxy") else dlgGauge:showxy(intPosX,intPosY) -- Show the Progress Bar window end doReset() -- Reset the Progress Bar display end end -- function Start function fh.Message(strText) -- Show the Progress Bar message if dlgGauge then lblText.Title = strText end end -- function Message function fh.Step(intStep) -- Step the Progress Bar forward if dlgGauge then intVal = intVal + ( intStep or 1 ) -- Default step is 1 local intNew = math.ceil( intVal / intMax * 100 * intScale ) / intScale if intPercent ~= intNew then -- Update progress once per percent or per second, whichever is smaller intPercent = math.max( 0.1, intNew ) -- Ensure percentage is greater than zero if intVal > intMax then intVal = intMax intPercent = 100 end -- Ensure values do not exceed maximum intNew = os.difftime(os.time(),intStart) if intDelta < intNew then -- Update clock of elapsed time intDelta = intNew intScale = math.ceil( intDelta / intPercent ) -- Scale of seconds per percentage step local intHour = math.floor( intDelta / 3600 ) local intMins = math.floor( intDelta / 60 - intHour * 60 ) local intSecs = intDelta - intMins * 60 - intHour * 3600 strClock = string.format("%02d : %02d : %02d",intHour,intMins,intSecs) end doUpdate() -- Update the Progress Bar display end iup.LoopStep() end end -- function Step function fh.Focus() -- Bring the Progress Bar window to front if dlgGauge then doFocus() end end -- function Focus function fh.Reset() -- Reset the Progress Bar display if dlgGauge then doReset() end end -- function Reset function fh.Stop() -- Check if Stop button pressed iup.LoopStep() return isBarStop end -- function Stop function fh.Close() -- Close the Progress Bar window isBarStop = false if dlgGauge then dlgGauge:destroy() dlgGauge = nil end end -- function Close function fh.Setup(tblSetup) -- Setup optional table of external attributes if tblSetup then tblBars = tblSetup strBack = tblBars.Back or strBack -- Background colour strBody = tblBars.Body or strBody -- Body text colour strFont = tblBars.Font or strFont -- Font dialogue strStop = tblBars.Stop or strStop -- Stop button colour intPosX = tblBars.X or intPosX -- Window position intPosY = tblBars.Y or intPosY end end -- function Setup return fh end -- local function progbar_v3 local progbar = progbar_v3() -- To access FH progress bars module --[[ @Module: +fh+iup_gui_v3 @Author: Mike Tate @Version: 3.9 @LastUpdated: 22 Jun 2021 @Description: Graphical User Interface Library Module @V3.9: ShowDialogue() popup closure fhSleep() added; CheckVersionInStore() at monthly intervals; @V3.8: Function Prototype Closure version. @V3.7: AssignAttributes(tblControls) now allows any string attribute to invoke a function. @V3.6: anyMemoDialogue() sets TopMost attribute. @V3.5: Replace IsNormalWindow(iupDialog) with SetWindowCoord(tblName) and update CheckWindowPosition(tblName) to prevent negative values freezing main dialog. @V3.4: Use general.MakeFolder() to ensure key folders exist, add Get/PutRegKey(), check Registry IE Shell Version in HelpDialogue(), better error handling in LoadSettings(). @V3.3: LoadFolder() and SaveFolder() use global folder as default for local folder to improve synch across PC. @V3.2: Load & Save settings now use a single clipboard so Local PC settings are preserved across synchronised PC. @V3.1: IUP 3.11.2 iup.GetGlobal("VERSION") to top, HelpDialogue conditional ExpandChildren="YES/NO", RefreshDialogue uses NaturalSize, SetUtf8Mode(), Load/SaveFolder(), etc @V3.0: ShowDialogue "dialog" mode for Memo, new DestroyDialogue, NewHelpDialogue tblAttr for Font, AssignAttributes intSkip, CustomDialogue iup.CENTERPARENT+, IUP Workaround, BalloonToggle, Initialise test Plugin file exists. @V2.0: Support for Plugin Data scope, new FontDialogue, RefreshDialogue, AssignAttributes, httpRequest handler, keep "dialog" mode. @V1.0: Initial version. ]] local function iup_gui_v3() local fh = {} -- Local environment table require "iuplua" -- To access GUI window builder require "iupluacontrols" -- To access GUI window controls require "lfs" -- To access LUA filing system require "iupluaole" -- To access OLE subsystem require "luacom" -- To access COM subsystem iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28 local iupVersion = iup.GetGlobal("VERSION") -- Obtain IUP module version -- "iuplua" Omitted Constants Workaround -- iup.TOP = iup.LEFT iup.BOTTOM = iup.RIGHT iup.RED = iup.RGB(1,0,0) iup.GREEN = iup.RGB(0,1,0) iup.BLUE = iup.RGB(0,0,1) iup.BLACK = iup.RGB(0,0,0) iup.WHITE = iup.RGB(1,1,1) iup.YELLOW = iup.RGB(1,1,0) -- Shared Interface Attributes & Functions -- fh.Version = " " -- Plugin Version fh.History = fh.Version -- Version History fh.Red = "255 0 0" -- Color attributes (must exclude leading zeros & spaces to allow value comparisons) fh.Maroon = "128 0 0" fh.Amber = "250 160 0" fh.Orange = "255 165 0" fh.Yellow = "255 255 0" fh.Olive = "128 128 0" fh.Lime = "0 255 0" fh.Green = "0 128 0" fh.Cyan = "0 255 255" fh.Teal = "0 128 128" fh.Blue = "0 0 255" fh.Navy = "0 0 128" fh.Magenta = "255 0 255" fh.Purple = "128 0 128" fh.Black = "0 0 0" fh.Gray = "128 128 128" fh.Silver = "192 192 192" fh.Smoke = "240 240 240" fh.White = "255 255 255" fh.Risk = fh.Red -- Risk colour for hazardous controls such as Close/Delete buttons fh.Warn = fh.Magenta -- Warn colour for caution controls and warnings fh.Safe = fh.Green -- Safe colour for active controls such as most buttons fh.Info = fh.Black -- Info colour for text controls such as labels/tabs fh.Head = fh.Black -- Head colour for headings fh.Body = fh.Black -- Body colour for body text fh.Back = fh.White -- Background colour for all windows fh.Gap = "8" -- Layout attributes Gap was "10" fh.Border = "8x8" -- was BigMargin="10x10" fh.Margin = "1x1" -- was MinMargin fh.Balloon = "NO" -- Tooltip balloon mode fh.FontSet = 0 -- Legacy GUI font set assigned by FontAssignment but used globally fh.FontHead = "" fh.FontBody = "" local GUI = { } -- Sub-table for GUI Dialogue attributes to allow any "Name" --[[ GUI.Name table of dialogue attributes, where Name is Font, Help, Main, Memo, Bars, etc GUI.Name.CoordX x co-ordinate ( Loaded & Saved by default ) GUI.Name.CoordY y co-ordinate ( Loaded & Saved by default ) GUI.Name.Dialog dialogue handle GUI.Name.Focus focus button handle GUI.Name.Frame dialogframe mode, "normal" = dialogframe="NO" else "YES", "showxy" = showxy(), "popup" or "keep" = popup(), default is "normal & showxy" GUI.Name.Height height GUI.Name.Raster rastersize ( Loaded & Saved by default ) GUI.Name.Width width GUI.Name.Back ProgressBar background colour GUI.Name.Body ProgressBar body text colour GUI.Name.Font ProgressBar font style GUI.Name.Stop ProgressBar Stop button colour GUI.Name.GUI Module table usable by other modules e.g. progbar.Setup Help dialogue Window attributes :- GUI.Help.GetHelp Parent dialogue GetHelp button GUI.Help.RootURL Wiki Help & Advice root URL GUI.Help.TblAttr Table of button attributes GUI.Help[n] Help dialogue nth button :- GUI.Help[n].Name Name for title attribute GUI.Help[n].Tip Tooltip for tip attribute GUI.Help[n].URL Page URL to append to root URL GUI.Help[n].Page Page order for intTabPosn --]] -- tblScrn[1] = origin x, tblScrn[2] = origin y, tblScrn[3] = width, tblScrn[4] = height local tblScrn = stringx.splitnumbers(iup.GetGlobal("VIRTUALSCREEN")) -- Used by CustomDialogue() and CheckWindowPosition() and ShowDialogue() below local intMaxW = tblScrn[3] local intMaxH = tblScrn[4] function fh.BalloonToggle() -- Toggle tooltips Balloon mode local tblToggle = { YES="NO"; NO="YES"; } fh.Balloon = tblToggle[fh.Balloon] fh.SaveSettings() end -- function BalloonToggle iup.SetGlobal("UTF8MODE","NO") function fh.SetUtf8Mode() -- Set IUP into UTF-8 display mode if iupVersion == "3.5" or stringx.encoding() == "ANSI" then return false end iup.SetGlobal("UTF8MODE","YES") iup.SetGlobal("UTF8MODE_FILE","NO") return true end -- function SetUtf8Mode local function tblOfNames(...) -- Get table of dialogue Names including "Font","Help","Main" by default local arg = {...} local tblNames = {"Font";"Help";"Main";} for intName, strName in ipairs(arg) do if type(strName) == "string" and strName ~= "Font" and strName ~= "Help" and strName ~= "Main" then table.insert(tblNames,strName) end end return tblNames end -- local function tblOfNames local function tblNameFor(strName) -- Get table of parameters for chosen dialogue Name strName = tostring(strName) if not GUI[strName] then -- Need new table with default minimum & raster size, and X & Y co-ordinates GUI[strName] = { } local tblName = GUI[strName] tblName.Raster = "x" tblName.CoordX = iup.CENTER tblName.CoordY = iup.CENTER end return GUI[strName] end -- local function tblNameFor local function intDimension(intMin,intVal,intMax) -- Return a number bounded by intMin and intMax if not intVal then return 0 end -- Except if no value then return 0 intVal = tonumber(intVal) or (intMin+intMax)/2 return math.max(intMin,math.min(intVal,intMax)) end -- local function intDimension function fh.CustomDialogue(strName,strRas,intX,intY) -- GUI custom window raster size, and X & Y co-ordinates -- strRas nil = old size, "x" or "0x0" = min size, "999x999" = new size -- intX/Y nil = central, "99" = co-ordinate position local tblName = tblNameFor(strName) local tblSize = {} local intWide = 0 local intHigh = 0 strRas = strRas or tblName.Raster if strRas then -- Ensure raster size is between minimum and screen size tblSize = stringx.splitnumbers(strRas) intWide = intDimension(intWide,tblSize[1],intMaxW) intHigh = intDimension(intHigh,tblSize[2],intMaxH) strRas = tostring(intWide.."x"..intHigh) end if intX and intX < iup.CENTERPARENT then intX = intDimension(0,intX,intMaxW-intWide) -- Ensure X co-ordinate positions window on screen end if intY and intY < iup.CENTERPARENT then intY = intDimension(0,intY,intMaxH-intHigh) -- Ensure Y co-ordinate positions window on screen end tblName.Raster = strRas or "x" tblName.CoordX = tonumber(intX) or iup.CENTER tblName.CoordY = tonumber(intY) or iup.CENTER end -- function CustomDialogue function fh.DefaultDialogue(...) -- GUI default window minimum & raster size, and X & Y co-ordinates for intName, strName in ipairs(tblOfNames(...)) do fh.CustomDialogue(strName) end end -- function DefaultDialogue function fh.DialogueAttributes(strName) -- Provide named Dialogue Attributes local tblName = tblNameFor(strName) -- tblName.Dialog = dialog handle, so any other attributes could be retrieved local tblSize = stringx.splitnumbers(tblName.Raster or "x") -- Split Raster Size into width=tblSize[1] and height=tblSize[2] tblName.Width = tblSize[1] tblName.Height= tblSize[2] tblName.Back = fh.Back -- Following only needed for NewProgressBar tblName.Body = fh.Body tblName.Font = fh.FontBody tblName.Stop = fh.Risk tblName.GUI = fh -- Module table return tblName end -- function DialogueAttributes local strDefaultScope = "Project" -- Default scope for Load/Save data is per Project/User/Machine as set by PluginDataScope() local tblClipProj = { } local tblClipUser = { } -- Clipboards of sticky data for each Plugin Data scope -- V3.2 local tblClipMach = { } local function doLoadData(strParam,strDefault,strScope) -- Load sticky data for Plugin Data scope strScope = tostring(strScope or strDefaultScope):lower() local tblClipData = tblClipProj if strScope:match("user") then tblClipData = tblClipUser elseif strScope:match("mach") then tblClipData = tblClipMach end return tblClipData[strParam] or strDefault end -- local function doLoadData function fh.LoadGlobal(strParam,strDefault,strScope) -- Load Global Parameter for all PC return doLoadData(strParam,strDefault,strScope) end -- function LoadGlobal function fh.LoadLocal(strParam,strDefault,strScope) -- Load Local Parameter for this PC return doLoadData(fh.ComputerName.."-"..strParam,strDefault,strScope) end -- function LoadLocal local function doLoadFolder(strFolder) -- Use relative paths to let Paths change -- V3.3 strFolder = strFolder:gsub("^FhDataPath",function() return fh.FhDataPath end) -- Full path to .fh_data folder strFolder = strFolder:gsub("^PublicPath",function() return fh.PublicPath end) -- Full path to Public folder strFolder = strFolder:gsub("^FhProjPath",function() return fh.FhProjPath end) -- Full path to project folder return strFolder end -- local function doLoadFolder function fh.LoadFolder(strParam,strDefault,strScope) -- Load Folder Parameter for this PC -- V3.3 local strFolder = doLoadFolder(fh.LoadLocal(strParam,"",strScope)) if not general.FlgFolderExists(strFolder) then -- If no local folder try global folder strFolder = doLoadFolder(fh.LoadGlobal(strParam,strDefault,strScope)) end return strFolder end -- function LoadFolder function fh.LoadDialogue(...) -- Load Dialogue Parameters for "Font","Help","Main" by default for intName, strName in ipairs(tblOfNames(...)) do local tblName = tblNameFor(strName) --# tblName.Raster = tostring(fh.LoadLocal(strName.."S",tblName.Raster)) -- Legacy of "S" becomes "R" tblName.Raster = tostring(fh.LoadLocal(strName.."R",tblName.Raster)) tblName.CoordX = tonumber(fh.LoadLocal(strName.."X",tblName.CoordX)) tblName.CoordY = tonumber(fh.LoadLocal(strName.."Y",tblName.CoordY)) fh.CheckWindowPosition(tblName) end end -- function LoadDialogue function fh.LoadSettings(...) -- Load Sticky Settings from File for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do strFileName = fh[strFileName] if general.FlgFileExists(strFileName) then -- Load Settings File in table lines with key & val fields local tblField = {} for strLine in io.lines(strFileName) do if #tblField == 0 and strLine == "return {" -- Unless entire Sticky Data table was saved and type(table.load) == "function" then local tblClip, strErr = table.load(strFileName) -- Load Settings File table if strErr then error(strErr.."\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.") end for i,j in pairs (tblClip) do tblClipData[i] = tblClip[i] end break end tblField = stringx.split(strLine,"=") if tblField[1] then tblClipData[tblField[1]] = tblField[2] end end end end fh.Safe = tostring(fh.LoadGlobal("SafeColor",fh.Safe)) fh.Warn = tostring(fh.LoadGlobal("WarnColor",fh.Warn)) fh.Risk = tostring(fh.LoadGlobal("RiskColor",fh.Risk)) fh.Head = tostring(fh.LoadGlobal("HeadColor",fh.Head)) fh.Body = tostring(fh.LoadGlobal("BodyColor",fh.Body)) fh.FontHead= tostring(fh.LoadGlobal("FontHead" ,fh.FontHead)) fh.FontBody= tostring(fh.LoadGlobal("FontBody" ,fh.FontBody)) fh.FontSet = tonumber(fh.LoadGlobal("Fonts" ,fh.FontSet)) -- Legacy only fh.FontSet = tonumber(fh.LoadGlobal("FontSet" ,fh.FontSet)) -- Legacy only fh.History = tostring(fh.LoadGlobal("History" ,fh.History)) fh.Balloon = tostring(fh.LoadGlobal("Balloon" ,fh.Balloon, "Machine")) fh.LoadDialogue(...) if fh.FontSet > 0 then fh.FontAssignment(fh.FontSet) end -- Legacy only end -- function LoadSettings local function doSaveData(strParam,anyValue,strScope) -- Save sticky data for Plugin Data scope strScope = tostring(strScope or strDefaultScope):lower() local tblClipData = tblClipProj if strScope:match("user") then tblClipData = tblClipUser elseif strScope:match("mach") then tblClipData = tblClipMach end tblClipData[strParam] = anyValue end -- local function doSaveData function fh.SaveGlobal(strParam,anyValue,strScope) -- Save Global Parameter for all PC doSaveData(strParam,anyValue,strScope) end -- function SaveGlobal function fh.SaveLocal(strParam,anyValue,strScope) -- Save Local Parameter for this PC doSaveData(fh.ComputerName.."-"..strParam,anyValue,strScope) end -- function SaveLocal function fh.SaveFolder(strParam,strFolder,strScope) -- Save Folder Parameter for this PC strFolder = stringx.replace(strFolder,fh.FhDataPath,"FhDataPath") -- Full path to .fh_data folder strFolder = stringx.replace(strFolder,fh.PublicPath,"PublicPath") -- Full path to Public folder strFolder = stringx.replace(strFolder,fh.FhProjPath,"FhProjPath") -- Full path to project folder --# doSaveData(fh.ComputerName.."-"..strParam,strFolder,strScope) -- Uses relative paths to let Paths change fh.SaveGlobal(strParam,strFolder,strScope) -- V3.3 fh.SaveLocal(strParam,strFolder,strScope) -- Uses relative paths to let Paths change end -- function SaveFolder function fh.SaveDialogue(...) -- Save Dialogue Parameters for "Font","Help","Main" by default for intName, strName in ipairs(tblOfNames(...)) do local tblName = tblNameFor(strName) fh.SaveLocal(strName.."R",tblName.Raster) fh.SaveLocal(strName.."X",tblName.CoordX) fh.SaveLocal(strName.."Y",tblName.CoordY) end end -- function SaveDialogue function fh.SaveSettings(...) -- Save Sticky Settings to File fh.SaveDialogue(...) fh.SaveGlobal("SafeColor",fh.Safe) fh.SaveGlobal("WarnColor",fh.Warn) fh.SaveGlobal("RiskColor",fh.Risk) fh.SaveGlobal("HeadColor",fh.Head) fh.SaveGlobal("BodyColor",fh.Body) fh.SaveGlobal("FontHead" ,fh.FontHead) fh.SaveGlobal("FontBody" ,fh.FontBody) fh.SaveGlobal("History" ,fh.History) fh.SaveGlobal("Balloon" ,fh.Balloon, "Machine") for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do for i,j in pairs (tblClipData) do -- Check if table has any entries strFileName = fh[strFileName] if type(table.save) == "function" then -- Save entire Settings File table per Project/User/Machine table.save(tblClipData,strFileName) else local fileHandle = general.OpenFile(strFileName,"w") -- Else save Settings File lines with key & val fields for strKey,strVal in pairs(tblClipData) do fileHandle:write(strKey.."="..strVal.."\n") end fileHandle:close() end break end end end -- function SaveSettings function fh.CheckWindowPosition(tblName) -- Ensure dialogue window coordinates are on Screen if tonumber(tblName.CoordX) == nil or tonumber(tblName.CoordX) < 0 -- V3.5 or tonumber(tblName.CoordX) > intMaxW then tblName.CoordX = iup.CENTER end if tonumber(tblName.CoordY) == nil or tonumber(tblName.CoordY) < 0 -- V3.5 or tonumber(tblName.CoordY) > intMaxH then tblName.CoordY = iup.CENTER end end -- function CheckWindowPosition function fh.IsNormalWindow(iupDialog) -- Check dialogue window is not Maximised or Minimised (now redundant) -- tblPosn[1] = origin x, tblPosn[2] = origin y, tblPosn[3] = width, tblPosn[4] = height local tblPosn = stringx.splitnumbers(iupDialog.ScreenPosition) local intPosX = tblPosn[1] local intPosY = tblPosn[2] if intPosX < 0 and intPosY < 0 then -- If origin is negative (-8, -8 = Maximised, -3200, -3200 = Minimised) return false -- then is Maximised or Minimised end return true end -- function IsNormalWindow function fh.SetWindowCoord(tblName) -- Set the Window coordinates if not Maximised or Minimised -- V3.5 -- tblPosn[1] = origin x, tblPosn[2] = origin y, tblPosn[3] = width, tblPosn[4] = height local tblPosn = stringx.splitnumbers(tblName.Dialog.ScreenPosition) local intPosX = tblPosn[1] local intPosY = tblPosn[2] if intPosX < 0 and intPosY < 0 then -- If origin is negative (-8, -8 = Maximised, -3200, -3200 = Minimised) return false -- then is Maximised or Minimised end tblName.CoordX = intPosX -- Otherwise set the Window coordinates tblName.CoordY = intPosY return true end -- function SetWindowCoord function fh.ShowDialogue(strName,iupDialog,btnFocus,strFrame) -- Set standard frame attributes and display dialogue window local tblName = tblNameFor(strName) iupDialog = iupDialog or tblName.Dialog -- Retrieve previous parameters if needed btnFocus = btnFocus or tblName.Focus strFrame = strFrame or tblName.Frame strFrame = strFrame or "show norm" -- Default frame mode is dialog:showxy(X,Y) with DialogFrame="NO" ("normal" to vary size, otherwise fixed size) strFrame = strFrame:lower() -- Other modes are "show", "popup" & "keep" with DialogFrame="YES", or with "normal" for DialogFrame="NO" ("show" for active windows, "popup"/"keep" for modal windows) if strFrame:gsub("%s-%a-map%a*[%s%p]*","") == "" then -- May be prefixed with "map" mode to just map dialogue initially, also may be suffixed with "dialog" to inhibit iup.MainLoop() to allow progress messages strFrame = "map show norm" -- If only "map" mode then default to "map show norm" end if type(iupDialog) == "userdata" then tblName.Dialog = iupDialog tblName.Focus = btnFocus -- Preserve parameters tblName.Frame = strFrame iupDialog.Background = fh.Back -- Background colour iupDialog.Shrink = "YES" -- Sometimes needed to shrink controls to raster size if type(btnFocus) == "userdata" then -- Set button as focus for Esc and Enter keys iupDialog.StartFocus = iupDialog.StartFocus or btnFocus iupDialog.DefaultEsc = iupDialog.DefaultEsc or btnFocus iupDialog.DefaultEnter = iupDialog.DefaultEnter or btnFocus end iupDialog.MaxSize = intMaxW.."x"..intMaxH -- Maximum size is screen size iupDialog.MinSize = "x" -- Minimum size (default "x" becomes nil) iupDialog.RasterSize = tblName.Raster or "x" -- Raster size (default "x" becomes nil) if strFrame:match("norm") then -- DialogFrame mode is "NO" by default for variable size window if strFrame:match("pop") or strFrame:match("keep") then iupDialog.MinBox = "NO" -- For "popup" and "keep" hide Minimize and Maximize icons iupDialog.MaxBox = "NO" else strFrame = strFrame.." show" -- If not "popup" nor "keep" then use "showxy" mode end else iupDialog.DialogFrame = "YES" -- Define DialogFrame mode for fixed size window end iupDialog.close_cb = iupDialog.close_cb or function() return iup.CLOSE end -- Define default window X close, move, and resize actions iupDialog.move_cb = iupDialog.move_cb or function(self) fh.SetWindowCoord(tblName) end -- V3.5 iupDialog.resize_cb = iupDialog.resize_cb or function(self) if fh.SetWindowCoord(tblName) then tblName.Raster=self.RasterSize end end -- V3.5 if strFrame:match("map") then -- Only dialogue mapping is required iupDialog:map() --# tblName.Frame = strFrame:gsub("map","") -- Remove "map" from frame mode ready for subsequent call tblName.Frame = strFrame:gsub("%s-%a-map%a*[%s%p]*","") -- Remove "map" from frame mode ready for subsequent call return end fh.RefreshDialogue(strName) -- Refresh to set Natural Size as Minimum Size if iup.MainLoopLevel() == 0 -- Called from outside Main GUI, so must use showxy() and not popup() or strFrame:match("dialog") or strFrame:match("sho") then -- Use showxy() to dispay dialogue window for "showxy" or "dialog" mode iupDialog:showxy(tblName.CoordX,tblName.CoordY) if fh.History ~= fh.Version then -- Initially show new Version History Help if type(fh.HelpDialogue) == "function" then fh.History = fh.Version fh.HelpDialogue(fh.Version) -- But only after Help dialogue exists iupDialog.BringFront = "YES" end end if not ( strName == "Help" or strFrame:match("dialog") ) -- Inhibit MainLoop if Help dialogue or "dialog" mode and iup.MainLoopLevel() == 0 then iup.MainLoop() end else iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to display dialogue window for "popup" or "keep" modes fhSleep(200,150) -- Sometimes needed to prevent MainLoop() closure! -- V3.9 end if not strFrame:match("dialog") and strFrame:match("pop") then tblName.Dialog = nil -- When popup closed, clear key parameters, but not for "keep" mode tblName.Raster = nil tblName.CoordX = nil -- iup.CENTER tblName.CoordY = nil -- iup.CENTER else fh.SetWindowCoord(tblName) -- Set Window coordinate pixel values -- V3.5 end end end -- function ShowDialogue function fh.DestroyDialogue(strName) -- Destroy existing dialogue local tblName = tblNameFor(strName) if tblName then local iupDialog = tblName.Dialog if type(iupDialog) == "userdata" then iupDialog:destroy() tblName.Dialog = nil -- Prevent future misuse of handle -- 22 Jul 2014 end end end -- function DestroyDialogue local function strDialogueArgs(strArgA,strArgB,comp) -- Compare two argument pairs and return matching pair local tblArgA = stringx.splitnumbers(strArgA) local tblArgB = stringx.splitnumbers(strArgB) local strArgX = tostring(comp(tblArgA[1] or 100,tblArgB[1] or 100)) local strArgY = tostring(comp(tblArgA[2] or 100,tblArgB[2] or 100)) return strArgX.."x"..strArgY end -- local function strDialogueArgs function fh.RefreshDialogue(strName) -- Refresh dialogue window size after Font change, etc local tblName = tblNameFor(strName) local iupDialog = tblName.Dialog -- Retrieve the dialogue handle if type(iupDialog) == "userdata" then iupDialog.Size = iup.NULL iupDialog.MinSize = iup.NULL -- V3.1 iup.Refresh(iupDialog) -- Refresh window to Natural Size and set as Minimum Size if not iupDialog.RasterSize then iupDialog:map() iup.Refresh(iupDialog) end local strSize = iupDialog.NaturalSize or iupDialog.RasterSize -- IUP 3.5 NaturalSize = nil, IUP 3.11 needs NaturalSize -- V3.1 iupDialog.MinSize = strDialogueArgs(iupDialog.MaxSize,strSize,math.min) -- Set Minimum Size to smaller of Maximm Size or Natural/Raster Size -- V3.1 iupDialog.RasterSize = strDialogueArgs(tblName.Raster,strSize,math.max) -- Set Current Size to larger of Current Size or Natural/Raster Size -- V3.1 iup.Refresh(iupDialog) tblName.Raster = iupDialog.RasterSize if iupDialog.Visible == "YES" then -- Ensure visible dialogue origin is on screen tblName.CoordX = math.max(tblName.CoordX,10) tblName.CoordY = math.max(tblName.CoordY,10) -- Set both coordinates to larger of current value or 10 pixels if iupDialog.Modal then -- V3.8 if iupDialog.Modal == "NO" then iupDialog.ZOrder = "BOTTOM" -- Ensure dialogue is subservient to any popup iupDialog:showxy(tblName.CoordX,tblName.CoordY) -- Use showxy() to reposition main window else iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to reposition modal window end end else iupDialog.BringFront="YES" end end end -- function RefreshDialogue function fh.AssignAttributes(tblControls) -- Assign the attributes of all controls supplied local anyFunction = nil for iupName, tblAttr in pairs ( tblControls or {} ) do if type(iupName) == "userdata" and type(tblAttr) == "table" then-- Loop through each iup control local intSkip = 0 -- Skip counter for attributes same for all controls for intAttr, anyName in ipairs ( tblControls[1] or {} ) do -- Loop through each iup attribute local strName = nil local strAttr = nil local strType = type(anyName) if strType == "string" then -- Attribute is different for each control in tblControls strName = anyName strAttr = tblAttr[intAttr-intSkip] elseif strType == "table" then -- Attribute is same for all controls as per tblControls[1] intSkip = intSkip + 1 strName = anyName[1] strAttr = anyName[2] elseif strType == "function" then intSkip = intSkip + 1 anyFunction = anyName break end if type(strName) == "string" and ( type(strAttr) == "string" or type(strAttr) == "function" ) then local anyRawGet = rawget(fh,strAttr) -- Use rawget() to stop require("pl.strict") complaining if type(anyRawGet) == "string" then strAttr = anyRawGet -- Use internal module attribute such as Head or FontBody elseif type(iupName[strName]) == "string" and type(strAttr) == "function" then -- Allow string attributes to invoke a function -- V3.7 strAttr = strAttr() end iupName[strName] = strAttr -- Assign attribute to control end end end end if anyFunction then anyFunction() end -- Perform any control assignment function end -- function AssignAttributes -- Font Dialogue Attributes and Functions -- fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default font for Body and Head text fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold ") ---[=[ local intFontPlain = 1 -- Font Face & Style values for legacy FontSet setting local intFontBold = 2 local intArialPlain = 3 local intArialBold = 4 local intTahomaPlain= 5 local intTahomaBold = 6 local strFontFace = fh.FontBody:gsub(",.*","") local tblFontSet = {} -- Lookup table for FontHead and FontBody tblFontSet[intFontPlain] = { Head=strFontFace.."; Bold -16"; Body=strFontFace.."; -15"; } tblFontSet[intFontBold] = { Head=strFontFace.."; Bold -16"; Body=strFontFace.."; Bold -15"; } tblFontSet[intArialPlain] = { Head="Arial; Bold -16"; Body="Arial; -16"; } tblFontSet[intArialBold] = { Head="Arial; Bold -16"; Body="Arial; Bold -15"; } tblFontSet[intTahomaPlain] = { Head="Tahoma; Bold -15"; Body="Tahoma; -16"; } tblFontSet[intTahomaBold] = { Head="Tahoma; Bold -15"; Body="Tahoma; Bold -14"; } function fh.FontAssignment(intFontSet) -- Assign Font Face & Style GUI values for legacy FontSet setting if intFontSet then intFontSet = math.max(intFontSet,1) intFontSet = math.min(intFontSet,#tblFontSet) fh.FontHead = tblFontSet[intFontSet]["Head"] -- Legacy Font for all GUI dialog header text fh.FontBody = tblFontSet[intFontSet]["Body"] -- Legacy Font for all GUI dialog body text end end -- function FontAssignment --]=] function fh.FontDialogue(tblAttr,strName) -- GUI Font Face & Style Dialogue tblAttr = tblAttr or {} strName = strName or "Main" local isFontChosen = false local btnFontHead = iup.button { Title="Choose Headings Font and default Colour"; } local btnFontBody = iup.button { Title="Choose Body text Font and default Colour"; } local btnCol_Safe = iup.button { Title=" Safe Colour "; } local btnCol_Warn = iup.button { Title=" Warning Colour "; } local btnCol_Risk = iup.button { Title=" Risky Colour "; } local btnDefault = iup.button { Title=" Default Fonts "; } local btnMinimum = iup.button { Title=" Minimum Size "; } local btnDestroy = iup.button { Title=" Close Dialogue "; } local frmSetFonts = iup.frame { Title=" Set Window Fonts & Colours "; iup.vbox { Alignment="ACENTER"; Margin=fh.Margin; Homogeneous="YES"; btnFontHead; btnFontBody; iup.hbox { btnCol_Safe; btnCol_Warn; btnCol_Risk; Homogeneous="YES"; }; iup.hbox { btnDefault ; btnMinimum ; btnDestroy ; Homogeneous="YES"; }; } -- iup.vbox end } -- iup.frame end -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local dialogFont = iup.dialog { Title=" Set Window Fonts & Colours "; Gap=fh.Gap; Margin=fh.Border; frmSetFonts; } local tblButtons = { } local function setDialogues() -- Refresh the Main and Help dialogues local tblHelp = tblNameFor("Help") if type(tblHelp.Dialog) == "userdata" then -- Help dialogue exists fh.AssignAttributes(tblHelp.TblAttr) -- Assign the Help dialogue attributes fh.RefreshDialogue("Help") -- Refresh the Help window size & position end fh.AssignAttributes(tblAttr) -- Assign parent dialogue attributes fh.RefreshDialogue(strName) -- Refresh parent window size & position and bring infront of Help window fh.RefreshDialogue("Font") -- Refresh Font window size & position and bring infront of parent window end -- local function setDialogues local function getFont(strColor) -- Set font button function local strTitle = " Choose font style & default colour for "..strColor:gsub("Head","Heading").." text " local strValue = "Font"..strColor -- The font codes below are not recognised by iupFontDlg and result in empty font face! local strFont = rawget(fh,strValue):gsub(" Black,",","):gsub(" Light, Bold",","):gsub(" Extra Bold,",","):gsub(" Semibold,",",") local iupFontDlg = iup.fontdlg { Title=strTitle; Color=rawget(fh,strColor); Value=strFont; } iupFontDlg:popup() -- Popup predefined font dialogue if iupFontDlg.Status == "1" then if iupFontDlg.Value:match("^,") then -- Font face missing so revert to original font iupFontDlg.Value = rawget(fh,strValue) end fh[strColor] = iupFontDlg.Color -- Set Head or Body color attribute fh[strValue] = iupFontDlg.Value -- Set FontHead or FontBody font style fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes setDialogues() isFontChosen = true end end -- local function getFont local function getColor(strColor) -- Set colour button function local strTitle = " Choose colour for "..strColor:gsub("Warn","Warning"):gsub("Risk","Risky").." button & message text " local iupColorDlg = iup.colordlg { Title=strTitle; Value=rawget(fh,strColor); ShowColorTable="YES"; } iupColorDlg.DialogFrame="YES" iupColorDlg:popup() -- Popup predefined color dialogue fixed size window if iupColorDlg.Status == "1" then fh[strColor] = iupColorDlg.Value -- Set Safe or Warn or Risk color attribute fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes setDialogues() isFontChosen = true end end -- local function getColor local function setDefault() -- Action for Default Fonts button fh.Safe = fh.Green fh.Warn = fh.Magenta fh.Risk = fh.Red -- Set default colours fh.Body = fh.Black fh.Head = fh.Black fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default fonts for Body and Head text fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold") fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes setDialogues() isFontChosen = true end -- local function setDefault local function setMinimum() -- Action for Minimum Size button local tblName = tblNameFor(strName) local iupDialog = tblName.Dialog -- Retrieve the parent dialogue handle if type(iupDialog) == "userdata" then tblName.Raster = "10x10" -- Refresh parent window to Minimum Size & adjust position fh.RefreshDialogue(strName) end local tblFont = tblNameFor("Font") tblFont.Raster = "10x10" -- Refresh Font window to Minimum Size & adjust position fh.RefreshDialogue("Font") end -- local function setMinimum tblButtons = { { "Font" ; "FgColor" ; "Tip" ; "action" ; {"TipBalloon";"Balloon";} ; {"Expand";"YES";} ; }; [btnFontHead] = { "FontHead"; "Head"; "Choose the Heading text Font Face, Style, Size, Effects, and default Colour"; function() getFont("Head") end; }; [btnFontBody] = { "FontBody"; "Body"; "Choose the Body text Font Face, Style, Size, Effects, and default Colour" ; function() getFont("Body") end; }; [btnCol_Safe] = { "FontBody"; "Safe"; "Choose the colour for Safe operations" ; function() getColor("Safe") end; }; [btnCol_Warn] = { "FontBody"; "Warn"; "Choose the colour for Warning operations"; function() getColor("Warn") end; }; [btnCol_Risk] = { "FontBody"; "Risk"; "Choose the colour for Risky operations" ; function() getColor("Risk") end; }; [btnDefault ] = { "FontBody"; "Safe"; "Restore default Fonts and Colours"; function() setDefault() end; }; [btnMinimum ] = { "FontBody"; "Safe"; "Reduce window to its minimum size"; function() setMinimum() end; }; [btnDestroy ] = { "FontBody"; "Risk"; "Close this dialogue "; function() return iup.CLOSE end; }; [frmSetFonts] = { "FontHead"; "Head"; }; } fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep normal") -- Popup the Set Window Fonts dialogue: "keep normal" : vary size & posn, and remember size & posn -- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup normal") -- Popup the Set Window Fonts dialogue: "popup normal" : vary size & posn, but redisplayed centred -- fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep") -- Popup the Set Window Fonts dialogue: "keep" : fixed size, vary posn, and only remember posn -- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup") -- Popup the Set Window Fonts dialogue: "popup": fixed size, vary posn, but redisplayed centred dialogFont:destroy() return isFontChosen end -- function FontDialogue -- Help Dialogue Attributes and Functions fh.HelpDialogue = "" -- HelpDialogue must be declared for ShowDialogue local strHelpButtonActive = nil -- defaults to "YES" -- Help button active attribute mode used only in NewHelpDialogue function fh.NewHelpDialogue(btnGetHelp,strRootURL) -- Prototype for GUI Help Dialogue, with parent Help button, and web page root/namespace URL local tblHelp = tblNameFor("Help") local oleControl, btnDestroy, hboxHelp, dialogHelp, tblAttr -- Dialogue component upvalues if type(btnGetHelp) == "userdata" then btnGetHelp.Active = strHelpButtonActive if btnGetHelp.Active == "NO" then -- Help button inactive, so Help dialogue exists, so just update parent button tblHelp.GetHelp = btnGetHelp -- Allows successive parent GUI to share one Help dialogue return end end tblHelp.GetHelp = btnGetHelp strRootURL = strRootURL or fh.Plugin:gsub(" ","_"):lower() -- Default to Plugin name as Wiki namespace if strRootURL:match("^[%w_]+$") then -- Append Wiki namespace to Wiki root URL strRootURL = "http://www.fhug.org.uk/wiki/doku.php?id=plugins:help:"..strRootURL..":" end tblHelp.RootURL = strRootURL local intURL = 1 -- Index to Version History help page URL local tblURL = { } -- List of help page URL local tblAttr = { } -- Attribute table primarily for FontDialogue() tblHelp.TblAttr = tblAttr local function doCommonAction() -- Common action when creating/destroying Help dialogue local strMode = "NO" if tblHelp.Dialog then tblHelp.Dialog = nil -- Clear dialog handle strMode = nil -- Defaults to "YES" but more efficient to test else tblAttr = { {"Font";"FgColor";}; } -- Reset attribute table primarily for FontDialogue() tblHelp.TblAttr = tblAttr end if type(tblHelp.GetHelp) == "userdata" then -- Set parent dialogue Help button active mode tblHelp.GetHelp.Active = strMode end strHelpButtonActive = strMode end -- local function doCommonAction -- Save global Help button active mode function fh.HelpDialogue(anyPage) -- GUI Help Dialogue for chosen web page --[=[ Parameter anyPage can be one of several values: 1. Page number from 0 to index tblURL, often equal to intTabPosn. 2. Version to display Version History page for version chosen. 3. String with " "="_" and lowercase substring of a page name in tblURL. --]=] if not fh.GetRegKey("HKLM\\SOFTWARE\\Microsoft\\Internet Explorer\\MAIN\\FeatureControl\\FEATURE_BROWSER_EMULATION\\fh.exe") then fhMessageBox("\n The 'Help and Advice' web page has encountered a problem. \n\n The FH IE Shell version is undefined in the Windows Registry. \n\n So please run the 'Write Reg IE Shell Version' Plugin to rectify. \n") return end if not tblHelp.Dialog then doCommonAction() -- Create the WebBrowser based on its ProgID and connect it to LuaCOM oleControl = iup.olecontrol{ "Shell.Explorer.1"; designmode="NO"; } oleControl:CreateLuaCOM() btnDestroy = iup.button { Title="Close Window"; Tip="Close this Help and Advice window"; TipBalloon=fh.Balloon; Expand="HORIZONTAL"; Size="x10"; FgColor=fh.Risk; action=function() dialogHelp:destroy() doCommonAction() end; } hboxHelp = iup.hbox { Margin=fh.Margin; Homogeneous="NO"; } -- Create each GUI button with title, tooltip, color, action, etc, and table of web page URL for intButton, tblButton in ipairs(tblHelp) do local intPage = tblButton.Page or intButton local strURL = tblButton.URL if strURL:match("ver.-hist") then intURL = intPage end tblURL[intPage] = strURL local btnName = iup.button { Title=tblButton.Name; Tip=tblButton.Tip; TipBalloon=fh.Balloon; Expand=btnDestroy.Expand; Size=btnDestroy.Size; FgColor=fh.Safe; action=function() oleControl.com:Navigate(tblHelp.RootURL..strURL) end; } iup.Append(hboxHelp,btnName) tblAttr[btnName] = { "FontBody"; "Safe"; } end iup.Append(hboxHelp,btnDestroy) tblAttr[btnDestroy] = { "FontBody"; "Risk"; } local strExpChild = "NO" if iupVersion == "3.5" then strExpChild = "YES" end -- V3.1 for IUP 3.11.2 dialogHelp = iup.dialog { Title=fh.Plugin.." Help & Advice"; Font=fh.FontBody; iup.vbox { Alignment="ACENTER"; Gap=fh.Gap; Margin=fh.Border; ExpandChildren=strExpChild; -- V3.1 for IUP 3.11.2 oleControl; hboxHelp; }; close_cb = function() doCommonAction() end; } fh.ShowDialogue("Help",dialogHelp,btnDestroy) -- Show Help dialogue window and set tblHelp.Dialog = dialogHelp end anyPage = anyPage or 0 if type(anyPage) == "number" then -- Select page by Tab = Button = Help page index anyPage = math.max(1,math.min(#tblURL,anyPage+1)) anyPage = tblURL[anyPage] or "" elseif anyPage == fh.Version then -- Select the Version History features section anyPage = anyPage:gsub("[%s%p]","") anyPage = anyPage:gsub("^(%d)","V%1") anyPage = tblURL[intURL].."#features_of_"..anyPage elseif type(anyPage) == "string" then -- Select page by matching name text local strPage = anyPage:gsub(" ","_"):lower() anyPage = tblURL[1] or "" -- Default to first web page for intURL = 1, #tblURL do local strURL = tblURL[intURL] if strURL:match(strPage) then anyPage = strURL break end end else anyPage = tblURL[1] or "" -- Default to first web page end oleControl.com:Navigate(tblHelp.RootURL..anyPage) -- Navigate to chosen web page end -- function HelpDialogue end -- function NewHelpDialogue function fh.AddHelpButton(strName,strTip,strURL,intPage) -- Add button to GUI Help Dialogue local tblHelp = tblNameFor("Help") if tblHelp and not strHelpButtonActive then for intHelp, tblHelp in ipairs(tblHelp) do -- Check button does not already exist if tblHelp.Name == strName then return end end if tonumber(intPage) then intPage = intPage + 1 end -- Optional external intPage number matches intTabPosn table.insert( tblHelp, { Name=strName or "?"; Tip=strTip or "?"; URL=strURL or ""; Page=intPage; } ) end end -- function AddHelpButton local function anyMemoControl(anyName,fgColor) -- Compose any control Title and FgColor local strName = tostring(anyName) -- anyName may be a string, and fgColor is default FgColor local tipText = nil if type(anyName) == "table" then -- anyName may be a table = { Title string ; FgColor string ; ToolTip string (optional); } strName = anyName[1] fgColor = anyName[2]:match("%d* %d* %d*") or fgColor tipText = anyName[3] end return strName, fgColor, tipText end -- local function anyMemoControl local function anyMemoDialogue(strHead,anyHead,strMemo,anyMemo,...) -- Display framed memo dialogue with buttons local arg = {...} -- Fix for Lua 5.2+ local intButt = 0 -- Returned value if "X Close" button is used local tblButt = { [0]="X Close"; } -- Button names lookup table local strHead, fgcHead, tipHead = anyMemoControl(anyHead or "",strHead) local strMemo, fgcMemo, tipMemo = anyMemoControl(anyMemo or "",strMemo) -- Create the GUI labels and buttons local lblMemo = iup.label { Title=strMemo; FgColor=fgcMemo; Tip=tipMemo; TipBalloon=fh.Balloon; Alignment="ACENTER"; Padding=fh.Margin; Expand="YES"; WordWrap="YES"; } local lblLine = iup.label { Separator="HORIZONTAL"; } local iupHbox = iup.hbox { Homogeneous="YES"; } local btnButt = iup.button { } local strTop = "YES" -- Make dialogue TopMost -- V3.6 local strMode = "popup" if arg[1] == "Keep Dialogue" then -- Keep dialogue open for a progress message strMode = "keep dialogue" lblLine = iup.label { } if not arg[2] then strTop = "NO" end -- User chooses TopMost -- V3.6 else if #arg == 0 then arg[1] = "OK" end -- If no buttons listed then default to an "OK" button for intArg, anyButt in ipairs(arg) do local strButt, fgcButt, tipButt = anyMemoControl(anyButt,fh.Safe) tblButt[intArg] = strButt btnButt = iup.button { Title=strButt; FgColor=fgcButt; Tip=tipButt; TipBalloon=fh.Balloon; Expand="NO"; MinSize="80"; Padding=fh.Margin; action=function() intButt=intArg return iup.CLOSE end; } iup.Append( iupHbox, btnButt ) end end -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local iupMemo = iup.dialog { Title=fh.Plugin..fh.Version..strHead; TopMost=strTop; -- TopMost added -- V3.6 iup.vbox { Alignment="ACENTER"; Gap=fh.Gap; Margin=fh.Margin; iup.frame { Title=strHead; FgColor=fgcHead; Font=fh.FontHead; iup.vbox { Alignment="ACENTER"; Font=fh.FontBody; lblMemo; lblLine; iupHbox; }; }; }; } fh.ShowDialogue("Memo",iupMemo,btnButt,strMode) -- Show popup Memo dialogue window with righthand button in focus (if any) if strMode == "keep dialogue" then return lblMemo end -- Return label control so message can be changed iupMemo:destroy() return intButt, tblButt[intButt] -- Return button number & title that was pressed end -- local function anyMemoDialogue function fh.MemoDialogue(anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with "Memo" in frame return anyMemoDialogue(fh.Head,"Memo",fh.Body,anyMemo,...) end -- function MemoDialogue function fh.WarnDialogue(anyHead,anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with heading in frame return anyMemoDialogue(fh.Warn,anyHead,fh.Warn,anyMemo,...) end -- function WarnDialogue function fh.GetRegKey(strKey) -- Read Windows Registry Key Value local luaShell = luacom.CreateObject("WScript.Shell") local anyValue = nil if pcall( function() anyValue = luaShell:RegRead(strKey) end ) then return anyValue -- Return Key Value if found end return nil end -- function GetRegKey function fh.PutRegKey(strKey,anyValue,strType) -- Write Windows Registry Key Value local luaShell = luacom.CreateObject("WScript.Shell") local strAns = nil if pcall( function() strAns = luaShell:RegWrite(strKey,anyValue,strType) end ) then return true end return nil end -- function PutRegKey local function httpRequest(strRequest) -- Luacom http request protected by pcall() below local http = luacom.CreateObject("winhttp.winhttprequest.5.1") http:Open("GET",strRequest,false) http:Send() return http.Responsebody end -- local function httpRequest function fh.VersionInStore(strPlugin) -- Obtain the Version in Plugin Store by Name only -- V3.9 local strVersion = "0" if strPlugin then local strFile = fh.MachinePath.."\\VersionInStore "..strPlugin..".dat" local intTime = os.time() - 2600000 -- Time in seconds a month ago -- V3.9 local tblAttr, strError = lfs.attributes(strFile) -- Obtain file attributes if not tblAttr or tblAttr.modification < intTime then -- File does not exist or was modified long ago -- V3.9 general.SaveStringToFile(strFile,strFile) -- Update file modified time local strFile = fh.MachinePath.."\\VersionInStoreInternetError.dat" local strRequest ="http://www.family-historian.co.uk/lnk/checkpluginversion.php?name="..strPlugin local isOK, strReturn = pcall(httpRequest,strRequest) if not isOK then -- Problem with Internet access local intTime = os.time() - 36000 -- Time in seconds 10 hours ago local tblAttr, strError = lfs.attributes(strFile) -- Obtain file attributes if not tblAttr or tblAttr.modification < intTime then -- File does not exist or was modified long ago fhMessageBox(strReturn.."\n The Internet appears to be inaccessible. ") end general.SaveStringToFile(strFile,strFile) -- Update file modified time else general.DeleteFile(strFile) -- Delete file if Internet is OK if strReturn ~= nil then strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits end end end end return strVersion or "0" end -- function VersionInStore local function intVersion(strVersion) -- Convert version string to comparable integer local intVersion = 0 local tblVersion = stringx.split(strVersion,".") for i=1,5 do intVersion = intVersion * 1000 + tonumber(tblVersion[i] or 0) end return intVersion end -- local function intVersion function fh.CheckVersionInStore() -- Check if later Version available in Plugin Store local strNewVer = fh.VersionInStore(fh.Plugin:gsub(" %- .*","")) local strOldVer = fh.Version if intVersion(strNewVer) > intVersion(strOldVer:match("%D*([%d%.]*)")) then fh.MemoDialogue("Later Version "..strNewVer.." of this Plugin is available from the Family Historian 'Plugin Store'.") end end -- function CheckVersionInStore function fh.PluginDataScope(strScope) -- Set default Plugin Data scope to per-Project, or per-User, or per-Machine strScope = tostring(strScope):lower() if strScope:match("mach") then -- Per-Machine strDefaultScope = "Machine" elseif strScope:match("user") then -- Per-User strDefaultScope = "User" end -- Per-Project is default end -- function PluginDataScope local function strToANSI(strFileName) if stringx.encoding() == "ANSI" then return strFileName end return fhConvertUTF8toANSI(strFileName) end -- local function strToANSI local function getPluginDataFileName(strScope) -- Get plugin data filename for chosen scope local isOK, strDataFile = pcall(fhGetPluginDataFileName,strScope) if not isOK then strDataFile = fhGetPluginDataFileName() end -- Before V5.0.8 parameter is disallowed and default = CURRENT_PROJECT return strToANSI(strDataFile) end -- local function getPluginDataFileName local function getDataFiles(strScope) -- Compose the Plugin Data file & path & root names local strPluginName = strToANSI(fh.Plugin) local strPluginPlain = stringx.plain(strPluginName) local strDataFile = getPluginDataFileName(strScope) -- Allow plugins with variant filenames to use same plugin data files strDataFile = strDataFile:gsub("\\"..strPluginPlain:gsub(" ","_"):lower(),"\\"..strPluginName) strDataFile = strDataFile:gsub("\\"..strPluginPlain..".+%.[D,d][A,a][T,t]$","\\"..strPluginName..".dat") if strDataFile == "" and strScope == "CURRENT_PROJECT" then -- Use standalone GEDCOM path & filename..".fh_data\Plugin Data\" as the folder + the Plugin Filename..".dat" strDataFile = strToANSI(fhGetContextInfo("CI_GEDCOM_FILE")) strDataFile = strDataFile:gsub("%.[G,g][E,e][D,d]",".fh_data") --# lfs.mkdir(strDataFile) general.MakeFolder(strDataFile) -- V3.4 strDataFile = strDataFile.."\\Plugin Data" --# lfs.mkdir(strDataFile) general.MakeFolder(strDataFile) -- V3.4 strDataFile = strDataFile.."\\"..strPluginName..".dat" end local strDataPath = strDataFile:gsub("\\"..strPluginPlain.."%.[D,d][A,a][T,t]$","") -- Plugin data folder path name local strDataRoot = strDataPath.."\\"..strPluginName -- Plugin data file root name general.MakeFolder(strDataPath) -- V3.4 return strDataFile, strDataPath, strDataRoot end -- local function getDataFiles function fh.Initialise(strVersion,strPlugin) -- Initialise the GUI module with optional Version & Plugin name local strAppData = strToANSI(fhGetContextInfo("CI_APP_DATA_FOLDER")) fh.Plugin = fhGetContextInfo("CI_PLUGIN_NAME") -- Plugin Name from file fh.Version = strVersion or " " -- Plugin Version if fh.Version == " " then local strTitle = "\n@Title is missing" local strAuthor = "\n@Author is missing" local strVersion = "\n@Version is missing" local strPlugin = strAppData.."\\Plugins\\"..fh.Plugin..".fh_lua" if general.FlgFileExists(strPlugin) then for strLine in io.lines(strPlugin) do -- Read each line from the Plugin file strPlugin = strLine:match("^@Title:[\t-\r ]*(.*)") if strPlugin then strPlugin = strPlugin:gsub("&&","&") --? if fh.Plugin:match("^"..strPlugin:gsub("(%W)","%%%1")) then if fh.Plugin:match("^"..stringx.plain(strPlugin)) then fh.Plugin = strPlugin -- Prefer Title to Filename if it matches strTitle = nil else strTitle = "\n@Title differs from Filename" -- Report abnormality end end if strLine:match("^@Author:%s*(.*)") then -- Check @Author exists strAuthor = nil end fh.Version = strLine:gsub("^@Version:%D*([%d%.]*)%D*"," %1 ") if fh.Version ~= strLine then -- Obtain the @Version from Plugin file strVersion = nil break end end if strTitle or strAuthor or strVersion then -- Report any header abnormalities fhMessageBox("\nScript Header: "..fh.Plugin..(strTitle or "")..(strAuthor or "")..(strVersion or "")) end else fhMessageBox("\nPlugin has not been saved!") end end fh.History = fh.Version -- Version History fh.Plugin = strPlugin or fh.Plugin -- Plugin Name from argument or default from file fh.CustomDialogue("Help","1020x730") -- Custom "Help" dialogue sizes fh.DefaultDialogue() -- Default "Font","Help","Main" dialogues fh.MachineFile,fh.MachinePath,fh.MachineRoot = getDataFiles("LOCAL_MACHINE") -- Plugin data names per machine fh.PerUserFile,fh.PerUserPath,fh.PerUserRoot = getDataFiles("CURRENT_USER") -- Plugin data names per user fh.ProjectFile,fh.ProjectPath,fh.ProjectRoot = getDataFiles("CURRENT_PROJECT") -- Plugin data names per project fh.FhDataPath = strToANSI(fhGetContextInfo("CI_PROJECT_DATA_FOLDER")) -- Paths used by Load/SaveFolder for relative folders fh.PublicPath = strToANSI(fhGetContextInfo("CI_PROJECT_PUBLIC_FOLDER")) -- Public data folder path name if fh.FhDataPath == "" then fh.FhDataPath = fh.ProjectPath:gsub("\\Plugin Data$","") end if fh.PublicPath == "" then fh.PublicPath = fh.ProjectPath fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Plugin Data$","%1") else general.MakeFolder(fh.PublicPath) -- V3.4 fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Public$","%1") end fh.CalicoPie = strAppData:gsub("\\Calico Pie\\.*","\\Calico Pie") -- Program Data Calico Pie path name fh.ComputerName = os.getenv("COMPUTERNAME") -- Local PC Computer Name end -- function Initialise fh.Initialise() -- Initialise module with default values return fh end -- local function iup_gui_v3 local iup_gui = iup_gui_v3() -- To access FH IUP GUI build module require "imlua" -- To access digital imaging library to convert Media image frames require "imlua_process" -- Preset Global Data Definitions -- function PresetGlobalData() iup_gui.Gap = "2" iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V4.0 IsIupUtf8 = iup_gui.SetUtf8Mode() fhSetStringEncoding("ANSI") -- Disable UTF-8 encoding -- V4.2 IntFhVersion = fhGetAppVersion() StrImport = fhGetContextInfo("CI_GEDCOM_FILE") -- Full path and filename of Project GEDCOM file to import if not general.FlgFileExists( StrImport ) then -- V4.2 error("\nThis GEDCOM file does not appear to exist:\n\n"..StrImport.."\n\n") end StrPath, StrFile, StrType = general.SplitFilename(StrImport) StrFile = StrFile:gsub("%."..StrType.."$","") -- Remove file type extension from filename TblOption = { } -- Table of GUI sticky options TblMode = { } -- Table of Modes of GEDCOM Export -- Abbreviation & Full product name for Basic Options ; Tick options for bottom of Extra Options tab ; @ handling ; table.insert(TblMode, { Abbr="Std"; Full="Standard GEDCOM Release 5.5" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- Prune added -- V1.9 -- Add Tidy & Fact & Pref & Capt and change Prune to AtAt -- V3.3 table.insert(TblMode, { Abbr="Str"; Full="Standard GEDCOM Release 5.5.1" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- V2.8 -- V4.0 table.insert(TblMode, { Abbr="AQP"; Full="Ancestral Quest Program" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- V3.8 table.insert(TblMode, { Abbr="AGS"; Full="Ancestris Genealogy Software" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- V3.8 table.insert(TblMode, { Abbr="ANC"; Full="Ancestry Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V2.2 table.insert(TblMode, { Abbr="FH5"; Full="Family Historian V5.0" ; Tidy="OFF"; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) if IntFhVersion > 6 then table.insert(TblMode, { Abbr="FH6"; Full="Family Historian V6.2" ; Tidy="OFF"; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- V4.0 end table.insert(TblMode, { Abbr="FST"; Full="Family Search Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V3.2 table.insert(TblMode, { Abbr="FTA"; Full="Family Tree Analyzer" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="FTD"; Full="Family Tree DNA" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V3.3 table.insert(TblMode, { Abbr="FTL"; Full="Family Tree Maker (legacy)" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V4.0 table.insert(TblMode, { Abbr="FTM"; Full="Family Tree Maker 2019" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V4.0 table.insert(TblMode, { Abbr="FMP"; Full="FindMyPast Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V2.2 table.insert(TblMode, { Abbr="GWC"; Full="GEDmill Website Creator" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- V3.3 table.insert(TblMode, { Abbr="GST"; Full="GedSite" ; Tidy="OFF"; Fact="ON" ; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- V3.1 table.insert(TblMode, { Abbr="GSP"; Full="GedStar Pro for Android" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="GRT"; Full="Genes Reunited Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="GFT"; Full="Gramps Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="HER"; Full="Heredis by BSD Concept" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt=StrSection; }) -- Heredis mishandles @ in Notes -- V4.0 was "at" but now "" table.insert(TblMode, { Abbr="LFT"; Full="Legacy Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="MFT"; Full="My Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt="@@"; }) -- V4.0 table.insert(TblMode, { Abbr="MYH"; Full="MyHeritage Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V3.2 table.insert(TblMode, { Abbr="RFT"; Full="RootsFinder Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V3.8 -- V4.0 "@@" => "@" table.insert(TblMode, { Abbr="RMT"; Full="Roots Magic Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="RWW"; Full="RootsWeb WorldConnect" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="TNG"; Full="The Next Generation" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="TPT"; Full="TribalPages Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V3.1 table.insert(TblMode, { Abbr="ZPG"; Full="ZoomPast Genealogy" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) -- V2.0 table.insert(TblMode, { Abbr="CEA"; Full="Custom Export Alpha" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) table.insert(TblMode, { Abbr="CEB"; Full="Custom Export Bravo" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; AtAt= "@"; }) local EncodeANSI = encoder.StrUTF8_ANSI -- V1.4 FH V5 Encode functions local EncodeISO = encoder.StrUTF8_ISO local EncodeUTF8 = tostring local EncodeUTF16= encoder.StrUTF8_UTF16 TblChar = { } -- Table of Character Encodings table.insert(TblChar, { Abbr="ANSI" ; Full="ANSI/Windows Code Page 1252" ; BOM="" ; Encode=EncodeANSI ; Mode="Ansi tng" ; }) -- V1.4 tng inhibits ANSI -- V1.9 Heredis & V2.0 Legacy were ANSI -- V3.8 Heredis~2019 & V4.0 Legacy~9 now UTF-8 table.insert(TblChar, { Abbr="ISO" ; Full=" ISO-8859-1 as used by TNG" ; BOM="" ; Encode=EncodeISO ; Mode=" " ; }) -- V3.1 TNG removed table.insert(TblChar, { Abbr="UTF8" ; Full=" UTF-8 with a Byte Order Mark" ; BOM=""; Encode=EncodeUTF8 ; Mode="Utf8" ; }) -- BOM = string.char(0xEF,0xBB,0xBF) = "" table.insert(TblChar, { Abbr="UTF8" ; Full=" UTF-8 without Byte Order Mark"; BOM="" ; Encode=EncodeUTF8 ; Mode=" " ; }) table.insert(TblChar, { Abbr="UTF16"; Full="UTF-16 with a Byte Order Mark" ; BOM="" ; Encode=EncodeUTF16; Mode=" " ; }) -- BOM = string.char(0xFF,0xFE) = "" -- V1.6 table.insert(TblChar, { Abbr="UTF16"; Full="UTF-16 without Byte Order Mark"; BOM="" ; Encode=EncodeUTF16; Mode=" " ; }) TblObje ={ } -- Table of Media Object options table.insert(TblObje, { Abbr="PART~LMO"; Full="Part Frames via Local Media Objects" ; Mode="All" ; }) -- V1.8 revised options table.insert(TblObje, { Abbr="FULL~LMO"; Full="Full Frames via Local Media Objects" ; Mode=" " ; }) table.insert(TblObje, { Abbr= "ALL~LMO"; Full=" All Frames via Local Media Objects" ; Mode="MYH" ; }) -- V3.2 MYH table.insert(TblObje, { Abbr="PART~ABS"; Full="Part Frames via Absolute Link Records"; Mode=" " ; }) table.insert(TblObje, { Abbr="FULL~ABS"; Full="Full Frames via Absolute Link Records"; Mode=" " ; }) table.insert(TblObje, { Abbr= "ALL~ABS"; Full=" All Frames via Absolute Link Records"; Mode="FTL FTM" ; }) -- V4.0 FTL table.insert(TblObje, { Abbr="FILE~REL"; Full="Multimedia via Relative Link Records" ; Mode="FH5 FH6" ; }) -- V4.0 FH6 table.insert(TblObje, { Abbr="FILE~ABS"; Full="Multimedia via Absolute Link Records" ; Mode="GST" ; }) -- V3.1 table.insert(TblObje, { Abbr="WIPE~ALL"; Full="Remove all the Multimedia entirely" ; Mode="Std- Str- ANC FMP FST FTA FTD GRT RFT RWW TPT ZPG" ; }) -- V2.8 Mdr -- V3.1 TPT -- V3.3 Std- Str- FST FTD -- V3.8 RFT -- V4.0 RWW TblPriv = { } -- Table of [[private]] text options -- V3.2 -- V4.0 table.insert(TblPriv, { Abbr="EXCLUDE"; Full="Exclude all the [[private]] text entirely"; Mode=" " ; }) table.insert(TblPriv, { Abbr="INCLUDE"; Full="Include private text but remove the [[ ]]"; Mode=" All " ; }) table.insert(TblPriv, { Abbr="KEEPALL"; Full="Keep all private text including the [[ ]]"; Mode=" FH5 FH6 GST" ; }) -- V4.0 FH6 TblRich = { } -- Table of rich text format options -- V4.0 local strLegacy = (" "):gsub("<(.-)>",StrLessTagMore) -- HTML tag replacement "%1" -- V4.0 table.insert(TblRich, { Abbr="REMOVE" ; Full="Remove rich text codes leaving just plain text" ; Mode=" All " ; }) table.insert(TblRich, { Abbr="REDUCE" ; Full="Reduce rich text to "; Mode=" GSP RMT" ; }) table.insert(TblRich, { Abbr="LEGACY" ; Full="Reduce rich text to "..strLegacy ; Mode=" LFT " ; }) table.insert(TblRich, { Abbr="HTML 5" ; Full="Change rich text into HTML 5 format" ; Mode=" FMP MYH RFT TNG TPT ZPG" ; }) table.insert(TblRich, { Abbr="RETAIN" ; Full="Retain rich text in Family Historian format" ; Mode=" GST " ; }) TblName = { } -- Table of part/full frame naming options -- V4.0 table.insert(TblName, "(PREFIX) Filename Prefix" ) table.insert(TblName, "(SUFFIX) Filename Suffix" ) table.insert(TblName, "(FOLDER) Sub-folder Name" ) TblLabs = { } -- Table of default synthetic Note labels, etc -- V3.3 -- V4.0 Help added table.insert(TblLabs, { Name="~Given Name" ; Help="Name (_USED) Given Name Used" ; }) table.insert(TblLabs, { Name="~Name Given" ; Help="Name (GIVN) Given" ; }) table.insert(TblLabs, { Name="~Name Tail" ; Help="Name /Surname/ Tail" ; }) table.insert(TblLabs, { Name="~Name Suffix" ; Help="Name (NSFX) Name Suffix" ; }) table.insert(TblLabs, { Name="~Surname Prefix" ; Help="Name (SPFX) Surname Prefix" ; }) table.insert(TblLabs, { Name="~Surname" ; Help="Name (SURN) Surname" ; }) if IntFhVersion > 6 then table.insert(TblLabs, { Name="Phonetic" ; Help="Name (FONE) Phonetic Variation" ; }) -- V4.0 table.insert(TblLabs, { Name="Romanised" ; Help="Name (ROMN) Romanized Variation"; }) -- V4.0 end table.insert(TblLabs, { Name="Name Note" ; Help="(NOTE) Name Note" ; }) -- V4.0 table.insert(TblLabs, { Name="Associate" ; Help="(ASSO) Associated Person" ; }) table.insert(TblLabs, { Name="Relationship" ; Help="(RELA) Relationship" ; }) table.insert(TblLabs, { Name="Descriptor" ; Help="(TYPE) Type/Descriptor" ; }) table.insert(TblLabs, { Name="Custom Ident" ; Help="(REFN) Custom Ident" ; }) table.insert(TblLabs, { Name="Automated Id" ; Help="(RIN) Automated Ident" ; }) -- V4.0 table.insert(TblLabs, { Name="Attribute Value" ; Help="(_ATTR) Attribute Value" ; }) if IntFhVersion > 6 then table.insert(TblLabs, { Name="Witness Role" ; Help="(ROLE) Witness Role" ; }) -- V4.0 See below end table.insert(TblLabs, { Name="Fact Cause" ; Help="(CAUS) Fact Cause" ; }) if IntFhVersion > 6 then table.insert(TblLabs, { Name="Fact Flags" ; Help="(_FLGS) Fact Flags" ; }) -- V4.0 end table.insert(TblLabs, { Name="Fact Address" ; Help="(ADDR) Fact Address" ; }) table.insert(TblLabs, { Name="Postal Address" ; Help="(ADDR) Postal Address" ; }) table.insert(TblLabs, { Name="E-mail Address" ; Help="(EMAIL) E-mail Address" ; }) table.insert(TblLabs, { Name="Website Address" ; Help="(WEB/WWW) Website Address" ; }) table.insert(TblLabs, { Name="Phone Number" ; Help="(PHON) Phone Number" ; }) if IntFhVersion > 6 then table.insert(TblLabs, { Name="Fax Number" ; Help="(FAX) Fax Number" ; }) -- V4.0 else table.insert(TblLabs, { Name="Witness Role" ; Help="(ROLE) Witness Role" ; }) -- V4.0 See above end table.insert(TblLabs, { Name="Original Date" ; Help="(DATE) Original Fact Date" ; }) -- V3.3 table.insert(TblLabs, { Name="Record Flag" ; Help="(_FLGS) Record Flag" ; }) -- V4.0 moved table.insert(TblLabs, { Name="Into Place" ; Help="(_PLAC) Into 2nd Place" ; }) table.insert(TblLabs, { Name="From Place" ; Help="(_PLAC) From 2nd Place" ; }) table.insert(TblLabs, { Name="Standardized" ; Help="(STAN) Standard Place Name" ; }) -- V4.0 table.insert(TblLabs, { Name="Place Status" ; Help="(STAT) Place Record Status" ; }) -- V4.0 table.insert(TblLabs, { Name="Individual's Age" ; Help="(AGE) Individual Fact Age" ; }) table.insert(TblLabs, { Name="Male Partner Age" ; Help="(AGE) Family Male Partner Age" ; }) table.insert(TblLabs, { Name="Marriage Status" ; Help="(_STAT) Marriage Status" ; }) -- V4.0 moved table.insert(TblLabs, { Name="Lady Partner Age" ; Help="(AGE) Family Lady Partner Age" ; }) table.insert(TblLabs, { Name="Source Type" ; Help="(_TYPE) Source Record Type" ; }) table.insert(TblLabs, { Name="Short Title" ; Help="(ABBR) Source Short Title" ; }) table.insert(TblLabs, { Name="Publication Info" ; Help="(PUBL) Publication Info" ; }) table.insert(TblLabs, { Name="Text From Source" ; Help="(TEXT) Text From Source" ; }) if IntFhVersion > 6 then table.insert(TblLabs, { Name="Repository" ; Help="(REPO) Repository Link" ; }) -- V4.0 table.insert(TblLabs, { Name="Metafield" ; Help="(_FIELD) Source Metafield" ; }) -- V4.0 table.insert(TblLabs, { Name="Source Template" ; Help="(_SRCT) Source Template" ; }) -- V4.0 table.insert(TblLabs, { Name="Research Note" ; Help="(_RNOT) Research Note" ; }) -- V4.0 end table.insert(TblLabs, { Name="Cited Source" ; Help="(SOUR) Source Citation" ; }) -- V4.0 table.insert(TblLabs, { Name="Media Date" ; Help="(_DATE) Media Date" ; }) table.insert(TblLabs, { Name="Keywords" ; Help="(_KEYS) Media Keywords" ; }) table.insert(TblLabs, { Name="Picture Note" ; Help="(_NOTE/_NOTA) Picture Note" ; }) table.insert(TblLabs, { Name="Caption Note" ; Help="(NOTE/_NOTA) Caption Note" ; Tail=":\t"; }) -- Link/Note cannot be positioned as already a NOTE table.insert(TblLabs, { Name="Citation Entry Date" ; Help="(DATE) Citation Entry Date" ; }) -- V4.0 table.insert(TblLabs, { Name="Citation Note" ; Help="(NOTE) Citation Note" ; Tail=":\t"; }) -- Text From Source Citation Note -- V3.3 table.insert(TblLabs, { Name="Place Record" ; Help="(_PLAC) Place Record" ; Tail=": " ; }) table.insert(TblLabs, { Name="Submitter" ; Help="(SUBM) Submitter Record" ; Tail=": " ; }) -- Synthetic Source Titles table.insert(TblLabs, { Name="Submission" ; Help="(SUBN) Submission Record" ; Tail=": " ; }) table.insert(TblLabs, { Name="Whole Record Citation";Help="(SOUR) Citation"; Tail="" ; }) -- Synthetic Event Type ResetResultSet() end -- function PresetGlobalData -- Clear Result Set Arrays -- function ResetResultSet() ArrSort = {} -- Result Set numerical order or processing ArrRule = {} -- Result Set Rule number ArrTitl = {} -- Result Set Rule Title name ArrItem = {} -- Result Set Rule Action item ArrRecd = {} -- Result Set Record head text line ArrPntr = {} -- Result Set Record link buddy pointer ArrLine = {} -- Result Set Line number in import file ArrOrig = {} -- Result Set Text Line import from record ArrText = {} -- Result Set Text Line export into record -- V4.0 end -- function ResetResultSet -- Reset Options for Mode of GEDCOM Export -- function ResetGedcomOptions(tblMode,strFull,strNoToggles) local arrFull = { "+"; "-"; } local strAbbr = tblMode.Abbr -- V3.3 if strFull then arrFull = { strFull; } end TblOption[strAbbr] = { } TblOption[strAbbr].Full = strFull or "+" -- V3.3 for _, strFull in ipairs ( arrFull ) do local strMode = strAbbr..strFull -- V3.3 if not TblOption[strMode] then TblOption[strMode] = { } end -- v4.0 ArrOptionToggles = { "Tidy"; "Fact"; "Pref"; "Capt"; } if not strNoToggles then -- Inhibit for Basic Options tab Reset Options button -- V4.0 for intItem, strItem in ipairs ( ArrOptionToggles ) do TblOption[strMode][strItem] = tblMode[strItem] -- Default Tidy Names & Fact Set & Synth Prefix & Repeat Caption settings -- V3.3 end end local strAll = "Ansi" -- FH5- defaults to ANSI, but FH6+ defaults to UTF-8 if IntFhVersion > 5 then strAll = "Utf8" end for intChar, tblChar in ipairs ( TblChar ) do -- Per Mode Character Encoding setting if tblChar.Mode:match(strAll) or tblChar.Mode:match(strAbbr) then -- Character Encoding matches Mode of GEDCOM Export -- V3.3 TblOption[strMode].Char = intChar if tblChar.Mode:match(strAbbr) then break end -- Escape when Mode of GEDCOM Export matches -- V3.3 end end for intObje, tblObje in ipairs ( TblObje ) do -- Per Mode Media Object option setting local strObjeMode = " "..tblObje.Mode.." " if strObjeMode:match(" All ") or strObjeMode:match(" "..strAbbr.." ") or strObjeMode:matches(strMode) then -- Media Object option matches Mode of GEDCOM Export -- V3.3 TblOption[strMode].Obje = intObje end end for intPriv, tblPriv in ipairs ( TblPriv ) do -- Per Mode [[private]] text option setting -- V3.2 if tblPriv.Mode:match("All") or tblPriv.Mode:match(strAbbr) or tblPriv.Mode:matches(strMode) then -- [[private]] text option matches Mode of GEDCOM Export -- V3.3 -- V4.0 TblOption[strMode].Priv = intPriv end end for intRich, tblRich in ipairs ( TblRich ) do -- Per Mode rich text format option setting -- V4.0 if tblRich.Mode:match("All") or tblRich.Mode:match(strAbbr) or tblRich.Mode:matches(strMode) then -- Rich text option matches Mode of GEDCOM Export -- V4.0 TblOption[strMode].Rich = intRich end end end end -- function ResetGedcomOptions -- Reset Synthetic Note Labels -- -- V3.3 function ResetSyntheticLabels() StrBefore = 1 -- Relies on lstWhere GUI droplist values StrAfter = 2 TblOption.Labs = { } for intLabs, arrLabs in ipairs ( TblLabs ) do -- Per synthetic label option setting -- V3.3 local strName = arrLabs.Name local strTail = arrLabs.Tail or ":\t" -- Default synthetic label, and where in other text -- V3.3 TblOption.Labs[strName] = { Value=strName..strTail; Where=StrAfter; } end end -- function ResetSyntheticLabels function StrGedcomExportMode(strAbbr) -- Get GEDCOM Export mode and Full option -- V3.3 local strAbbr = strAbbr or TblMode[TblOption.Mode].Abbr local strFull = "+" if TblOption[strAbbr] then strFull = TblOption[strAbbr].Full or "+" end return strAbbr..strFull, strAbbr, strFull end -- function StrGedcomExportMode -- Reset Sticky Settings to Default Values -- function ResetDefaultSettings() iup_gui.CustomDialogue("Main","0x0") -- Custom "Main" dialogue minimum size & centralisation iup_gui.CustomDialogue("Font","0x0") -- Custom "Font" dialogue minimum size & centralisation iup_gui.DefaultDialogue("Bars") -- GUI window rastersize and X & Y co-ordinates for "Main","Font","Bars" dialogues IntTabPosn = 0 -- Tab position undefined TblOption = { } TblOption.Mode = 1 -- Default Mode of GEDCOM Export is "Std" for intMode, tblMode in ipairs ( TblMode ) do ResetGedcomOptions(tblMode) -- Set options for each Mode of GEDCOM Export -- V3.3 end ResetSyntheticLabels() -- V3.3 local strMode = StrGedcomExportMode() -- V3.3 TblOption.Char = TblOption[strMode].Char -- Default Character Encoding setting TblOption.Obje = TblOption[strMode].Obje -- Default Media Object option setting TblOption.Priv = TblOption[strMode].Priv -- Default exclude private Notes option -- V3.2 TblOption.Rich = TblOption[strMode].Rich -- Default rich text formatting option -- V4.0 TblOption.Name = TblOption.Name or 1 -- Default Media part-frame filename prefix, suffix, or folder -- V4.0 TblOption.High = TblOption.High or 600 -- Default Media image maximum pixel height TblOption.Wide = TblOption.Wide or 600 -- Default Media image maximum pixel width TblOption.Jpeg = TblOption.Jpeg or "OFF" -- Default JPEG file type setting -- V1.4 TblOption.Full = TblOption.Full or "OFF" -- Default Full frame applicable -- V3.7 TblOption.Used = TblOption.Used or 9 -- Default rule used limit in Result Set TblOption.Keep = TblOption.Keep or "OFF" -- Default keep Media subfolder option -- V2.9 local strPath = general.SplitFilename(StrImport) -- Project GEDCOM file import folder path StrExportOld = strPath:gsub("\\$","\\Export") local strFile = StrFile:plain().."%.fh_data\\$" if fhGetContextInfo("CI_APP_MODE") == "Project Mode" and strPath:match(strFile) then -- If a Project, then use Public folder -- V2.2 strPath = iup_gui.PublicPath.."\\" end StrExportNew = strPath:gsub("\\$","\\Export") -- Default GEDCOM file export folder path TblOption.Path = StrExportNew general.MakeFolder(StrExportNew,iup_gui.MemoDialogue) -- Ensure that this Export folder exists -- V2.9 end -- function ResetDefaultSettings -- Load Sticky Settings from File -- function LoadSettings() iup_gui.LoadSettings("Bars") -- Includes "Main","Font","Bars" dialogues and "FontSet" & "History" TblOption = iup_gui.LoadGlobal("Option",TblOption) TblOption.Path = iup_gui.LoadFolder("Path",StrExportNew) -- Fix synch of local Export folder -- V2.8 if TblOption.Path == StrExportNew and general.FlgFolderExists(StrExportOld) -- Report change of default Export folder -- V2.2 and StrExportNew ~= StrExportOld then -- But only if New and Old folders differ -- V2.6 local strMessage = [[ The new default Export folder path is: ]]..StrExportNew..[[ The old default Export folder path was: ]]..StrExportOld..[[ Please move old folder contents to new folder and delete old folder. ]] fhMessageBox((strMessage:gsub("\t",""))) end local intMode = tonumber(TblOption.Mode) if intMode then if intMode > 5 then TblOption.Mode = intMode + 1 end -- Adjust numerical Mode for insertion of GedStar Pro else if TblOption.Mode == "GSW" then TblOption.Mode = "GST" end -- V3.1 Temp Fix if TblOption.Mode == "GSB" then TblOption.Mode = "GST" end -- V3.1 Temp Fix if TblOption.Mode == "Sdr" then TblOption.Mode = "Str" end -- V4.0 Standard Release 5.5.1 fix if TblOption.Mode == "Min" then -- V3.3 TblOption.Mode = "Std" TblOption["Std"].Full = "-" end TblOption["Min"] = nil TblOption["Min+"] = nil TblOption["Min-"] = nil if TblOption.Mode == "Mdr" then -- V3.3 TblOption.Mode = "Str" TblOption["Str"].Full = "-" end TblOption["Mdr"] = nil TblOption["Mdr+"] = nil TblOption["Mdr-"] = nil for intMode, tblMode in ipairs ( TblMode ) do if TblOption.Mode == TblMode[intMode].Abbr then -- Convert textual Mode from Abbr to number TblOption.Mode = intMode break end end end if type(TblOption.Obje) == "string" then for intObje, tblObje in ipairs ( TblObje ) do if TblOption.Obje == TblObje[intObje].Abbr then -- Convert textual Obje from Abbr to number TblOption.Obje = intObje break end end end if type(TblOption.Priv) == "string" then local intPriv = nil if TblOption.Priv == "ON" then intPriv = 1 end -- Legacy adjustment if TblOption.Priv == "OFF" then intPriv = 2 end if intPriv then TblOption.Priv = intPriv for intMode, tblMode in ipairs ( TblMode ) do local strMode = tblMode.Abbr if TblOption[strMode] and not TblOption[strMode].Priv then if TblPriv[3].Mode:match(strMode) then -- FH5 & GST TblOption[strMode].Priv = 3 else TblOption[strMode].Priv = intPriv end end end end for intPriv, tblPriv in ipairs ( TblPriv ) do if TblOption.Priv == TblPriv[intPriv].Abbr then -- Convert textual Priv from Abbr to number -- V3.2 TblOption.Priv = intPriv break end end end if type(TblOption.Rich) == "string" then for intRich, tblRich in ipairs ( TblRich ) do if TblOption.Rich == TblRich[intRich].Abbr then -- Convert textual Rich from Abbr to number -- V4.0 TblOption.Rich = intRich break end end end local strHistory = iup_gui.History if #strHistory > 2 then if strHistory <= " 1.7 " then -- Perform legacy adjustments local strMessage = [[ This version]]..iup_gui.Version..[[has many new 'Extra Options' that will ALL be RESET. If you customised version]]..strHistory..[['Extra Options' you must customise again. However, the 'Basic Options' should all be preserved unaltered. ]] fhMessageBox((strMessage:gsub("\t",""))) TblOption.Path = iup_gui.LoadFolder("Path",TblOption.Path) -- V1.6 if not TblOption.High then TblOption.High = 600 end -- Set options (early legacy) if not TblOption.Wide then TblOption.Wide = 600 end if not TblOption.Used then TblOption.Used = 9 end if not TblOption.Char or not TblOption.Obje then -- Set mode options (early legacy) for intMode, tblMode in ipairs ( TblMode ) do ResetGedcomOptions(tblMode) -- Reset options for each Mode of GEDCOM Export (early legacy) -- V3.3 end local strMode = StrGedcomExportMode() -- V3.3 TblOption.Char = TblOption[strMode].Char or 1 TblOption.Obje = TblOption[strMode].Obje or 1 else local arrObje = { } -- Post V1.3 legacy if strHistory == " 1.7 " then arrObje = { 1, 2, 5, 7, 8 } -- Conversion from V1.7 to V1.8 Object options else arrObje = { 1, 2, 6, 8, } -- Conversion from earlier to V1.8 Object options end for intMode, tblMode in ipairs ( TblMode ) do -- Per Mode Media Object option setting local strMode = StrGedcomExportMode(tblMode.Abbr) -- V3.3 if not TblOption[strMode] then ResetGedcomOptions(tblMode) -- Default options for any missing Mode of GEDCOM Export -- V3.3 else local intChar = TblOption[strMode].Char or TblOption.Char or 1 local intObje = TblOption[strMode].Obje or TblOption.Obje or 1 ResetGedcomOptions(tblMode) -- Reset options for each Mode of GEDCOM Export as many have changed -- V3.3 TblOption[strMode].Char = intChar or 1 -- But preserve Char & Object option settings TblOption[strMode].Obje = arrObje[intObje] or 1 -- Adjust earlier options to V1.8 options end end end elseif strHistory <= " 1.9 " then -- Perform legacy adjustments -- V2.0 for intMode, tblMode in ipairs ( TblMode ) do local strMode = tblMode.Abbr if TblOption[strMode] then local intRule = TblOption[strMode]["0 @P%d+@"] or nil -- Place Record has new FH5 GSP rule if intRule then if intRule > 1 then intRule = intRule + 1 end -- So bump the others down one if strMode == "FH5" or strMode == "GSP" then intRule = 2 end -- Update FH5 & GSP TblOption[strMode]["0 @P%d+@"] = intRule end end end elseif strHistory <= " 2.5.2 " then -- Set (LFT) Legacy Family Tree setting for Note records TblOption["LFT"]["0 @N%d+@"] = "q" elseif #strHistory <= 7 and strHistory <= " 3.2.5 " then -- Adjust all the Object record settings -- V3.3 for intMode, tblMode in ipairs ( TblMode ) do local strMode = tblMode.Abbr if TblOption[strMode] then TblOption[strMode]["0 @O%d+@"] = nil end end elseif (#strHistory <= 7 and strHistory <= " 3.2.9 ") or strHistory == " 3.2.10 " or strHistory == " 3.2.11 " then TblOption["GST+"]["0 @I%d+@"] = nil -- Adjust the GST+ Ind/Fam record settings -- V3.3 TblOption["GST+"]["0 @F%d+@"] = nil TblOption["RMT+"]["0 @I%d+@"] = nil -- Adjust the RMT+ Ind/Fam record settings -- V3.3 TblOption["RMT+"]["0 @F%d+@"] = nil end for intMode, tblMode in ipairs ( TblMode ) do -- Convert settings for Mode to Mode..Full -- V3.3 local strAbbr = tblMode.Abbr local tblMode = TblOption[strAbbr] if tblMode then local strFull = tblMode.Full or "+" local strMode = StrGedcomExportMode(strAbbr) if not TblOption[strMode] then TblOption[strMode] = {} for strOpt, strVal in pairs ( tblMode ) do if strMode == "LFT+" then if strOpt == "1 _STAT" and strVal == "A" then strVal = "B" end if strOpt == "1 _ATTR" and strVal == "A" then strVal = "B" end end TblOption[strMode][strOpt] = strVal end TblOption[strAbbr] = {} TblOption[strAbbr].Full = strFull end end end end for intMode, tblMode in ipairs ( TblMode ) do local strAbbr = tblMode.Abbr for _, strFull in ipairs ( { "+"; "-"; } ) do -- All abbreviated Modes and Full/Brief option -- V4.0 local strMode = strAbbr..strFull if not TblOption[strMode] or not TblOption[strMode].Char or not TblOption[strMode].Obje then ResetGedcomOptions(tblMode) -- V1.8 -- V3.3 end if not TblOption[strMode].Priv then for intPriv, tblPriv in ipairs ( TblPriv ) do -- Per Mode [[private]] text option setting -- V3.2 if tblPriv.Mode:match("All") or tblPriv.Mode:match(strAbbr) or tblPriv.Mode:matches(strMode) then -- [[private]] text option matches Mode of GEDCOM Export TblOption[strMode].Priv = intPriv end end end if not TblOption[strMode].Rich then for intRich, tblRich in ipairs ( TblRich ) do -- Per Mode rich text format option setting -- V4.0 if tblRich.Mode:match("All") or tblRich.Mode:match(strAbbr) or tblRich.Mode:matches(strMode) then -- Rich text option matches Mode of GEDCOM Export TblOption[strMode].Rich = intRich end end end if TblOption[strMode].Date == "ON" then -- V1.4 TblOption[strMode]["%d DATE"] = 2 end TblOption[strMode].Date = nil -- Clear per Mode DATE tag option TblOption[strMode].Jpeg = nil -- Clear per Mode JPEG frame option -- V1.4 for intItem, strItem in ipairs ( ArrOptionToggles ) do if not TblOption[strMode][strItem] then TblOption[strMode][strItem] = tblMode[strItem] -- Initialise Tidy Names & Fact Set & Synth Prefix & Repeat Caption settings -- V3.3 end end end end TblOption.Name = TblOption.Name or 1 -- Default Media part-frame filename prefix, suffix, or folder -- V4.0 if not TblOption.Labs then -- Set synthetic note labels -- V3.3 ResetSyntheticLabels() end SaveSettings() -- Save sticky data settings end -- function LoadSettings -- Save Sticky Settings to File -- function SaveSettings() local intMode = TblOption.Mode -- Save the Mode using Abbr rather than number index if tonumber(intMode) then TblOption.Mode = TblMode[intMode].Abbr end local intObje = TblOption.Obje -- Save the Obje using Abbr rather than number index if tonumber(intObje) then TblOption.Obje = TblObje[intObje].Abbr end local intPriv = TblOption.Priv -- Save the Priv using Abbr rather than number index -- V3.2 if tonumber(intPriv) then TblOption.Priv = TblPriv[intPriv].Abbr end local intRich = TblOption.Rich -- Save the Rich using Abbr rather than number index -- V4.0 if tonumber(intRich) then TblOption.Rich = TblRich[intRich].Abbr end iup_gui.SaveGlobal("Option",TblOption) iup_gui.SaveFolder("Path",TblOption.Path) -- V1.6 iup_gui.SaveSettings("Bars") -- Includes "Main","Font","Bars" dialogues and "FontSet" & "History" TblOption.Mode = intMode TblOption.Obje = intObje TblOption.Priv = intPriv TblOption.Rich = intRich StrZipFMP = TblOption.Path.."\\ZipFMP\\" -- FMP folder for ZIP bulk Media files -- V3.3 end -- function SaveSettings function SetSharedData() -- Shared variables for MakeRules() & ExportGedcom(), etc -- V4.0 strGedImport = "5.5" -- GEDCOM Version of import file -- V4.0 strGedExport = "5.5" -- GEDCOM Version of export file -- V4.0 tblGedSelect = { -- GEDCOM variants mostly for Media, but also custom fact tag -- V4.0 ["5.5"] = { Type={ jpg="jpeg"; tif="tiff"; }; Lev=0; Msid="_ASID"; Fact="1 EVEN"; }; ["5.5.1"] = { Type={ jpeg="jpg"; tiff="tif"; }; Lev=1; Msid="_SEQ" ; Fact="1 FACT"; }; } strMsid = "_SEQ" -- Media seq id tag for GEDCOM 5.5 = _ASID and GEDCOM 5.5.1 = _SEQ -- V4.0 strFact = "1 _ATTR " -- Custom fact tag in doAnalyse() FH V6 = 1 _ATTR & FH V7 = 1 FACT but in useRules() GEDCOM 5.5 = 1 EVEN & GEDCOM 5.5.1 = 1 FACT -- V4.0 if IntFhVersion > 6 then strFact = "1 FACT " end -- Used in doSaveFacts() to set custom attribute tag -- V4.0 strMode, strAbbr, strFull = StrGedcomExportMode() -- GEDCOM Export mode Mode = Std+, FH5+, FTM-, FMP-, etc, Abbr = Std, FH5, FTM, TNG, etc, Full = + or - -- V3.3 strChar = TblChar[TblOption.Char].Abbr -- GEDCOM character encoding option ANSI, UTF8, UTF16, etc strObje = TblObje[TblOption.Obje].Abbr -- Media conversion GUI Abbr = PART~LMO, FULL~LMO, ALL~LMO, PART~ABS, FULL~ABS, ALL~ABS, FILE~REL, FILE~ABS, WIPE~ALL -- V1.8 strPriv = TblPriv[TblOption.Priv].Abbr -- [[private]] text GUI Abbr = EXCLUDE, INCLUDE, KEEPALL -- V3.2 strRich = TblRich[TblOption.Rich].Abbr -- Rich text format GUI Abbr = REMOVE, REDUCE, LEGACY, HTML 5, RETAIN -- V4.0 strAtAt = TblMode[TblOption.Mode].AtAt -- GEDCOM "@@" to single "@" or "" or keep, as many programs do not honour GEDCOM "@@" convention, only for setRules() and doPrune() -- V3.3 -- V1.9 dicName = { } -- Dictionary entry per record ident of its Name/Title to translate idents to names and for File Root record dicTidy = { } -- Dictionary of tidied Place and Address names for strTidyText() and doAnalyse() -- V3.3 isTidy = ( TblOption[strMode].Tidy == "ON" ) -- Tidy Place & Address fields, only for strTidyText() and doAnalyse() -- V3.3 isFact = ( TblOption[strMode].Fact == "ON" ) -- Export Fact Set Definitions, only for doFactDefs() -- V3.3 isCapt = ( TblOption[strMode].Capt == "ON" ) -- Avoid repeat Caption Notes, only for doObjRecord() -- V3.3 isPref = ( TblOption[strMode].Pref == "ON" ) -- Synthetic prefix omega/ZZ or none -- V3.3 strPrefix = "" -- Prefix on synthetic record names to distinguish from originals ~ No prefix required -- V3.3 if isPref then -- Synthetic prefix required -- V3.3 if ( strChar == "UTF16" or strChar == "UTF8" ) -- If Unicode and not FH5 or LFT set prefix to -- V2.0 moved here from doHeaderRec() and strAbbr ~= "FH5" and strAbbr ~= "LFT" then -- U+03A9 = Omega for synthetic names -- V1.8 strPrefix = encoder.StrUTF16_UTF8(string.char(0xA9)..string.char(0x03)).." " else strPrefix = "ZZ " -- Otherwise use prefix ZZ end end intFontSize, strFontName = iup_gui.GetRegKey("HKCU\\Software\\Calico Pie\\Family Historian\\2.0\\Preferences\\PDX Font"):match("^(%d+),[%d,]+(.-)$") intFontSize = ( intFontSize or 200 ) / 20 -- Default font size and name -- V4.5 end -- function SetSharedData() function MakeRules() -- Prototype for conversion Rules SetSharedData() -- V4.0 local tblRecord = {} -- Record to be converted by rules local tblLineNo = {} -- Line numbers of original lines local strRecT = "HEAD" -- Record level 0 tag code for isRecord(), etc -- V1.8 local intRecI = 1 -- Record level 0 id num for doContNote() -- V2.5 local strRec0 = "" -- Record level 0 first line -- V3.7 -- V1.8 local ptrRec0 = fhNewItemPtr() -- Record level 0 pointer -- V3.7 local intOrig = 0 -- Record import line number -- V3.7 local strOrig = "" -- Record import line text -- V3.7 local arrRule = {} -- Regular array of conversion rules for text tags local dicRule = {} -- Dictionary entry per text tag to supply each conversion rule local tblMode = {} -- Dictionary entry per text tag of possible & current rules & GUI titles & names, and regular arrays of multi-choice tags local dicNote = { NI=0; HI=0; MI=0; } -- Dictionary entry per Note Id for Legacy tabs Research and Medical -- V2.6 local dicObje = {} -- Dictionary entry per Media Id & ASID for Local Media Object image conversions local dicSour = {} -- Dictionary entry per Source Id for any associated Media ASID & Id local dicPlac = {} -- Dictionary entry per Place Id for conversions to Source record, or per Place Name for conversions to GEDCOM 5.5.1 MAP structure in doPlaceTag() -- V3.8 local dicCite = {} -- Dictionary entry per Place Name to citation Source Id for doPlaceTag() -- V1.8 local arrFact = {} -- Regular array of Witnessed Facts to copy into the Witness Individuals -- V2.0 local dicTitle = { } -- Dictionary of Media Titles to make unique in FMP -- V3.3 local dicLabel = { } -- Dictionary of synthetic Note labels -- V3.3 local dicWhere = { } -- Dictionary of whether before/after other text -- V3.3 local isNotFull = false -- Conditions for Media processing rules below local isLocObje = false local isAllType = false local isFileRel = false local isFileAbs = false -- V3.1 local isWipeAll = false local isWipeLmo = false -- True for WIPE~ALL or ****~LMO to remove all Media records local isCaption = false -- True for FULL~ABS or FILE~REL or FILE~ABS to keep Media record captions -- V3.3 local intNote = 1 -- Note Record Id for synthetic Note Records used by Named Lists & File Root & Research Notes local intSour = 1 -- Source Record Id for synthetic Source Records for converted Source Notes local intObje = 1 -- Media Record Id for synthetic Media Records for part frame images -- V1.8 local intCite = nil -- Synthetic "Local Note" Source Record Id for Ancestry doCiteNote() -- V2.2 local dicMaxId = { N=1; S=1; M=1; O=1; } local intParts = 1 -- Max number of Place name comma separated parts (for AGS Ancestris) -- V3.8 local intGarbage = 0 -- Count of converted GEDCOM lines used to invoke memory garbage collection -- V4.5 local function setResultSet(intSort,intRule,strOrig,strText) -- Update the Result Set arrays -- V4.5 -- intSort ~ Either #ArrSort+1 or 0 for reports -- intRule ~ Current export rule or report number -- -1 = Memory usage; -2 = Media error; -3 = JPEG Option error; -- strOrig ~ Imported line or filename -- strText ~ Exported line or filename or media error local strItem = tblRecord.Item or arrRule.Item or "?" if intRule == -1 then strItem = "Memory Usage Count" end if intRule == -3 then strItem = "JPEG option error" end table.insert(ArrSort,intSort or 0) -- Sequence number table.insert(ArrRule,intRule or 0) table.insert(ArrTitl,tblRecord.Title or arrRule.Title or "?") -- Rule details table.insert(ArrItem,strItem) table.insert(ArrRecd,strRec0) -- Record details table.insert(ArrPntr,ptrRec0) table.insert(ArrLine,intOrig) -- Imported line number table.insert(ArrOrig,strOrig) -- Imported line or filename table.insert(ArrText,strText) -- Exported line or filename or media error end -- local function setResultSet local function doReportMemoryUsage() -- Report memory usage in Result Set -- V4.5 setResultSet(0,-1,"Memory Usage Count","KB="..tostring(collectgarbage("count"))) end -- local function doReportMemoryUsage local function getElements(strRecId) -- Make rule pattern, ident & tag from supplied text; used by doListIds(), doFileRoot() -- strRecId ~ Record identifying letter & number local dicRecTag = { I=" INDI"; F=" FAM"; N=" NOTE"; S=" SOUR"; R=" REPO"; O=" OBJE"; P=" _PLAC"; U=" SUBM"; B=" SUBN"; E=" _RNOT"; T=" _SRCT"; } -- V4.0 added _RNOT & _SRCT local strInit, strRid = strRecId:match("([IFNSROPUBET])(%d+)") -- V4.0 added E & T return "0 @("..strInit..")("..strRid..")@", dicRecTag[strInit] end -- local function getElements local function getParts(strText) -- Break text line into component parts and match a Record @link@ or UDF @@link@@ -- V2.2 cater for "1 chan" and convert to uppercase -- strText ~ GEDCOM text to split into level, tag, value, and whether a record link local strLev, strTag, strVal = (strText or ""):match("^(%d) (@?[_%a%d]+@?) ?(.-)$") return tonumber(strLev or -1), (strTag or ""):upper(), strVal, (strVal or ""):match("^@@?%u+%d+@@?$") -- V2.6 %u+ for NI, HI, MI in Legacy end -- local function getParts local function strRecordName(strRecId) -- Lookup record name in dictionary -- strRecId ~ Record identifying letter & number return dicName[strRecId] or "[unknown record]" end -- local function strRecordName local function strRecordText(strRecId) -- Record Name Display Text -- V2.2 -- strRecId ~ Record identifying letter & number local strName = strRecordName(strRecId) if strRecId:match("^N") and #strName > 33 then -- Truncate long Note record text -- V2.6 strName = strName:sub(1,30).."..." end return "["..strRecId.."]\t"..strName end -- local function strRecordText local function doInsert(strText,intLine,intOrig,isFILE) -- Insert text line into record at record line number -- strText ~ GEDCOM text line -- intLine ~ Record line number (optional) -- intOrig ~ Original import line (optional) -- V4.0 -- isFILE ~ True to insert before FILE in tblRecord.Text -- V4.0 local intCurr = tblRecord.Line intLine = intLine or #tblRecord+1 -- If no record line number, insert at end of record intOrig = intOrig or tblLineNo[intCurr] -- If no import line number, use current one -- V4.0 if strFull == "-" then strText = strText:gsub("\t"," ") end -- In Brief mode convert invalid tab to space -- V4.0 table.insert(tblRecord,intLine,strText) -- Insert text into record array table.insert(tblLineNo,intLine,intOrig) -- Insert import line number of current line -- V4.0 if intLine <= intCurr then -- Inserted before current line so increment it -- V1.8 tblRecord.Line = intCurr + 1 end if strText:match("^%d FILE ") and not tblRecord.Text[#tblRecord.Text]:match(" @.%d+@ ") then tblRecord.FILE = #tblRecord.Text + 1 -- Note where FILE inserted except within OBJEct record -- V4.0 end if isFILE and (tblRecord.FILE or 99) <= #tblRecord.Text then table.insert(tblRecord.Text,tblRecord.FILE,strText) -- Insert OBJE export text line for Result Set -- V4.0 else if #tblRecord.Text < 9 then table.insert(tblRecord.Text,strText) -- Accumulate export text lines for Result Set -- V4.0 end end end -- local function doInsert local function doModify(strText,intLine) -- Modify text line existing in record at line number -- V4.0 -- strText ~ GEDCOM text line -- intLine ~ Record line number tblRecord[intLine] = strText if #tblRecord.Text < 9 then table.insert(tblRecord.Text,strText) -- Accumulate export text lines for Result Set -- V4.0 end end -- local function doModify local function doDelete(intLine) -- Delete text line from record at record line number -- V1.8 -- intLine ~ Record line number local intCurr = tblRecord.Line if intLine == intCurr then -- Except if at current line intLine = intLine + 1 else table.remove(tblRecord,intLine) -- Delete text from record array table.remove(tblLineNo,intLine) -- Delete the import line number to keep arrays in step if intLine < intCurr then -- Deletion before current line so decrement it -- V1.8 tblRecord.Line = intCurr - 1 end end return intLine end -- local function doDelete local function isRecord(strType) -- Does record match the supplied type(s) -- V1.3 -- strType ~ Record type tag list such as "INDI, FAM" return ( (strType or "?"):match(strRecT) ) -- V1.8 -- V3.8 end -- local function isRecord local strRuleKey = "^(%d? [^ ]+)" -- Rule dictionary key is optional level digit, a space, then tag/link e.g. "1 OBJE", "3 _ASID", "0 @I99@", " @P@" -- V1.6 local function doDropRule(arrRule) -- Remove redundant dictionary rules for run-time efficiency -- arrRule ~ Regular array of rule keys to remove for _, strRule in ipairs (arrRule) do dicRule[strRule:match(strRuleKey)] = nil end end -- local function doDropRule local dicNameTag = -- Tag lookup for doNameTag(), doMakeNote(), doAddr2Even(), doCustEvent(), doSaveFacts(), doAnyRecord(), doSortDates(), doMultiFact(), doRepoRec(), doRepoNote(), doObjNote(), doPlaceTag(), doDisJoin(), doPrune() -- V1.8 FTL -- V1.9 -- V4.0 --[[ E=0; INDI/FAM Events in doAddr2Even(), doCustEvent(), doSaveFacts() A=0; INDI/FAM Attributes may hold < > for GSP in doPrune() L=0; INDI/FAM LDS Ordinances in doCustEvent(), doPlaceTag() I=0; F=0; INDI/FAM level 1 Rid initial allows local Note in doAnyRecord(), doSortDates(), doMultiFact() K=2; Fact, Name, etc, level 2 tag to keep in doAnyRecord() I=1; F=1; INDI/FAM level 1 Rid initial to keep in doAnyRecord() N=1; S=1; NOTE/SOUR level 1 Rid initial to keep in doAnyRecord() O=1; P=1; OBJE/_PLAC level 1 Rid initial to keep in doAnyRecord() R=1; REPOsitory level 1 Rid initial to keep in doAnyRecord() & doRepoRec() M=1; OBJEct Media tag to move to Note in doObjNote() T=1; Ancestry Family Tree tag to keep in doRepoNote() X=1; Kept tag eXcluded from local Note in doAnyRecord() long=0; Class = longtext --]] { RECID = { Name="#) Record Id:\t"; }; HEAD = { Name="#) Header:\t"; }; -- HEADer record tags VERS = { Name="#) Version:\t"; }; CORP = { Name="#) Corporation:\t"; }; COPR = { Name="#) Copyright:\t"; }; DEST = { Name="#) Destination:\t"; }; FILE = { Name="#) Filename:\t"; }; GEDC = { Name="#) Gedcom:\t"; }; CHAR = { Name="#) Char.Encoding:\t"; }; _UID = { Name="#) Unique Id:\t"; }; _LIST = { Name="#) Named List:\t"; }; _FLAG = { Name="#) Editing Flag:\t"; }; _IDS = { Name="#) Named List Id:\t"; }; _ROOT = { Name="#) File Root:\t"; }; LANG = { Name="#) Language:\t"; }; -- SUBMitter record tag FAMF = { Name="#) Family File:\t"; }; -- SUBmissioN record tags ANCE = { Name="#) Ancestors:\t"; }; DESC = { Name="#) Descendants:\t"; }; -- Replaced by DESC Description later ORDI = { Name="#) Ordinance Flag:\t"; }; INDI = { Name="#) Individual:\t"; }; -- INDIvidual record name NAME = { Name="#) Name:\t\t"; T=1; I=0; R=1; }; -- I=0; F=0; for INDI/FAM level 1 Rid initial with own local Note in doAnyRecord() ASSO = { Name="#) Associate:\t"; I=0; }; ADOP = { Name="#) Adoption:\t"; E=0; I=0; }; -- E=0; I=0; INDIvidual Events BIRT = { Name="#) Birth:\t\t"; E=0; I=0; }; BAPM = { Name="#) Baptism:\t"; E=0; I=0; }; BARM = { Name="#) Bar Mitzvah:\t"; E=0; I=0; }; BASM = { Name="#) Bat Mitzvah:\t"; E=0; I=0; }; BLES = { Name="#) Blessing:\t"; E=0; I=0; }; BURI = { Name="#) Burial:\t\t"; E=0; I=0; }; CENS = { Name="#) Census: \t"; E=0; I=0; F=0; }; -- E=0; F=0; FAMily Event too CREM = { Name="#) Cremation:\t"; E=0; I=0; }; CHR = { Name="#) Christening:\t"; E=0; I=0; }; CHRA = { Name="#) Christen Adult:\t"; E=0; I=0; }; CONF = { Name="#) Confirmation:\t"; E=0; I=0; }; DEAT = { Name="#) Death:\t\t"; E=0; I=0; }; EMIG = { Name="#) Emigration:\t"; E=0; I=0; }; EVEN = { Name="#) Event:\t\t"; E=0; I=0; F=0; }; FCOM = { Name="#) 1st Communion:\t"; E=0; I=0; }; GRAD = { Name="#) Graduation:\t"; E=0; I=0; }; IMMI = { Name="#) Immigration:\t"; E=0; I=0; }; NATU = { Name="#) Naturalisation:\t"; E=0; I=0; }; ORDN = { Name="#) Ordination:\t"; E=0; I=0; }; PROB = { Name="#) Probate:\t"; E=0; I=0; }; RETI = { Name="#) Retirement:\t"; E=0; I=0; }; WILL = { Name="#) Will:\t\t"; E=0; I=0; }; CAST = { Name="#) Group/Caste:\t"; A=0; I=0; }; -- A=0; I=0; INDIvidual Attributes DSCR = { Name="#) Description:\t"; A=0; I=0; }; EDUC = { Name="#) Education:\t"; A=0; I=0; }; IDNO = { Name="#) National Id:\t"; A=0; I=0; }; NATI = { Name="#) Nationality:\t"; A=0; I=0; }; NCHI = { Name="#) Child Count:\t"; A=0; I=0; F=0; }; -- FAMily tag without Gedcom Note but becomes Custom Event with Note NMR = { Name="#) Marriage Count:\t"; A=0; I=0; }; OCCU = { Name="#) Occupation:\t"; A=0; I=0; }; PROP = { Name="#) Possessions:\t"; A=0; I=0; }; RELI = { Name="#) Religion:\t"; A=0; I=0; }; RESI = { Name="#) Residence:\t"; A=0; I=0; }; SSN = { Name="#) Soc.Sec.No:\t"; A=0; I=0; }; TITL = { Name="#) Title:\t\t"; S=1; I=0; O=1; long=0; }; -- Keep Source & Object Record level 1 tag -- A=0; not allowed as upsets Source & Object Titles -- V2.1 _ATTR = { Name="#) Attribute:\t"; A=0; I=0; F=0; }; -- A=0; F=0; FAMily Attribute too FACT = { Name="#) Attribute:\t"; A=0; I=0; F=0; }; -- V4.0 ANUL = { Name="#) Annulment:\t"; E=0; F=0; }; -- E=0; F=0; FAMily Events DIV = { Name="#) Divorce:\t"; E=0; F=0; }; DIVF = { Name="#) Divorce Filed:\t"; E=0; F=0; }; ENGA = { Name="#) Engagement:\t"; E=0; F=0; }; MARR = { Name="#) Marriage:\t"; E=0; F=0; }; MARB = { Name="#) Marr.Banns:\t"; E=0; F=0; }; MARC = { Name="#) Marr.Contract:\t"; E=0; F=0; }; MARL = { Name="#) Marr.Licence:\t"; E=0; F=0; }; MARS = { Name="#) Marr.Settled:\t"; E=0; F=0; }; BAPL = { Name="#) LDS Baptism:\t"; L=0; I=0; }; -- L=0; LDS Ordinances for doCustEvent(), doPlaceTag() CONL = { Name="#) LDS Confirmed:\t"; L=0; I=0; }; ENDL = { Name="#) LDS Endowment:\t"; L=0; I=0; }; SLGC = { Name="#) LDS Ch.Sealing:\t"; L=0; I=0; }; SLGS = { Name="#) LDS Sp.Sealing:\t"; L=0; F=0; }; SEX = { Name="#) Gender: \t"; I=1; }; -- I=1; F=1; N=1; S=1; P=1; R=1; O=1; Rid initials of Record level 1 to keep FAM = { Name="#) Family:\t"; }; -- FAMily record name FAMC = { Name="#) Parents:\t"; I=1; }; FAMS = { Name="#) Spouse: \t"; I=1; }; HUSB = { Name="#) Husband:\t"; F=1; }; WIFE = { Name="#) Wife:\t\t"; F=1; }; CHIL = { Name="#) Child:\t\t"; F=1; }; OBJE = { Name="#) Media Object:\t"; S=1; I=1; F=1; K=2; P=1; }; SOUR = { Name="#) Source Data:\t"; I=1; F=1; K=2;long=0;}; ABBR = { Name="#) Short Title:\t"; S=1; }; -- Short Title may become Title and always gets removed AUTH = { Name="#) Author: \t"; S=1; long=0;}; PUBL = { Name="#) Publication:\t"; S=1; long=0;}; REPO = { Name="#) Repository:\t"; S=1; }; TEXT = { Name="#) Text:\t\t"; long=0;}; -- V4.0 CALN = { Name="#) Identified:\t"; }; MEDI = { Name="#) Media Type:\t"; }; _TYPE = { Name="#) Source Type:\t"; }; _FIELD= { Name="#) Metafield:\t"; }; -- V4.0 PAGE = { Name="#) Page:\t\t"; }; DATA = { Name="#) Data:\t\t"; }; QUAY = { Name="#) Assessment:\t"; }; _QUAY = { Name="#) Assessment:\t"; }; -- V4.0 GIVN = { Name="#) Given Name:\t"; }; SURN = { Name="#) Surname:\t"; }; SPFX = { Name="#) Surn.Prefix:\t"; }; NICK = { Name="#) Nickname:\t"; K=2; }; -- K=2; for Fact level 2 tags to keep _USED = { Name="#) Name Used:\t"; K=2; }; NPFX = { Name="#) Name Prefix:\t"; K=2; }; NSFX = { Name="#) Name Suffix:\t"; K=2; }; FONE = { Name="#) Phonetic:\t"; K=2; }; -- V4.0 ROMN = { Name="#) Romanised:\t"; K=2; }; -- V4.0 TYPE = { Name="#) Type:\t\t"; K=2; }; DATE = { Name="#) Date:\t\t"; T=1; K=2; }; _SDATE= { Name="#) Sort Date:\t"; }; -- V4.0 CAUS = { Name="#) Cause: \t"; K=2; }; PLAC = { Name="#) Place:\t\t"; K=2; }; ADDR = { Name="#) Address:\t"; R=1; K=2; long=0; }; -- R=1; for doRepoRec() to keep tag ADR1 = { Name="#) 1st Line:\t"; }; ADR2 = { Name="#) 2nd Line:\t"; }; CITY = { Name="#) City:\t\t"; }; STAE = { Name="#) State:\t\t"; }; POST = { Name="#) Postcode:\t"; }; CTRY = { Name="#) Country:\t"; }; _PLAC = { Name="#) To/From:\t"; }; AGE = { Name="#) Aged:\t\t"; }; PHON = { Name="#) Phone No:\t"; R=1; }; FAX = { Name="#) Fax No:\t"; R=1; }; -- V4.0 _EMAIL= { Name="#) Email:\t\t"; R=1; }; EMAIL = { Name="#) Email:\t\t"; R=1; }; -- V4.0 _WEB = { Name="#) Web Site:\t"; R=1; }; WWW = { Name="#) Web Site:\t"; R=1; }; -- V4.0 AGNC = { Name="#) Agency: \t"; }; TEMP = { Name="#) Temple: \t"; }; FORM = { Name="#) Format: \t"; O=1; }; -- O=1; OBJEct Media record tags to keep BLOB = { Name="#) Binary Object:\t"; }; _FILE = { Name="#) Media File:\t"; O=1; }; _ASID = { Name="#) Auto Seq Id:\t"; O=1; }; _SEQ = { Name="#) Auto Seq Id:\t"; O=1; }; -- V4.0 _NOTA = { Name="#) Annotation:\t"; O=1; long=0; }; -- V4.0 _AREA = { Name="#) Frame Area:\t"; O=1; }; _EXCL = { Name="#) Exclude Flag:\t"; }; _CAPT = { Name="#) Caption Flag:\t"; }; _DATE = { Name="#) Media Date:\t"; M=1; }; -- M=1; for doObjNote() for GFT -- V1.9 _KEYS = { Name="#) Keywords:\t"; M=1; }; STAT = { Name="#) Status: \t"; }; -- Place Record & LDS Ordinance tag STAN = { Name="#) Standardised:\t"; }; -- Place Record tags MAP = { Name="#) Mapping:\t"; }; LATI = { Name="#) Latitude:\t"; }; LONG = { Name="#) Longitude:\t"; }; _STAT = { Name="#) Marr.Status:\t"; }; _SHAR = { Name="#) Witness:\t"; }; _SHAN = { Name="#) Witness Name:\t"; }; ROLE = { Name="#) Role:\t\t"; }; RELA = { Name="#) Relation:\t"; }; PEDI = { Name="#) Pedigree:\t"; }; _PEDI = { Name="#) Child Pedigree:\t"; }; ALIA = { Name="#) Alias:\t\t"; }; ANCI = { Name="#) Ancestor:\t"; }; DESI = { Name="#) Descendant:\t"; }; SUBM = { Name="#) Submitter:\t"; }; SUBN = { Name="#) Submission:\t"; }; AFN = { Name="#) Ancestor File:\t"; }; RFN = { Name="#) Permanent Ref:\t"; }; RIN = { Name="#) Automated Id:\t"; }; REFN = { Name="#) Custom Id:\t"; }; RESN = { Name="#) Restriction:\t"; }; _SENT = { Name="#) Sentence:\t"; }; _FLGS = { Name="#) Flags:\t\t"; }; _RNOT = { Name="#) Research Note:\t"; }; -- Research Note record -- V4.0 _SRCT = { Name="#) Source Template:\t";}; -- Source Template record -- V4.0 COLL = { Name="#) Collection\t"; }; -- Source Template tags -- V4.0 CATG = { Name="#) Category\t"; }; -- V4.0 SUBC = { Name="#) Subcategory\t"; }; -- V4.0 REFN = { Name="#) Reference\t"; }; -- V4.0 DESC = { Name="#) Description:\t"; long=0; }; -- V4.0 BIBL = { Name="#) Bibliography\t"; }; -- V4.0 FOOT = { Name="#) Footnote\t"; }; -- V4.0 SHRT = { Name="#) Short Footnote\t"; }; -- V4.0 FDEF = { Name="#) Field Def\t"; }; -- V4.0 CODE = { Name="#) Field Code\t"; }; -- V4.0 CITN = { Name="#) Field Citation\t"; }; -- V4.0 PROM = { Name="#) Field Prompt\t"; }; -- V4.0 _FIELD= { Name="#) Field:\t\t"; }; -- V4.0 ENUM = { Name="#) Enumerator:\t"; }; -- V4.0 URL = { Name="#) Internet URL:\t"; }; -- V4.0 _AUTO = { Name="#) Auto-Text:\t"; }; -- V4.0 _FMT = { Name="#) Format:\t\t"; }; -- V4.0 _LINK_I={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_F={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_N={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_S={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_R={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_O={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_P={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_B={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_U={ Name="#) Link Ref:\t"; }; -- V4.0 _LINK_E={ Name="#) Link Ref:\t"; }; -- V4.0 _LKID = { Name="#) Link Id:\t"; }; -- V4.0 _PCIT = { Name="#) Prep Citation:\t"; }; -- V4.0 CHAN = { Name="#) Changed:\t"; T=1; }; -- T=1; for doRepoNote() to keep tag for Ancestry TIME = { Name="#) Time:\t\t"; T=1; }; _NOTE = { Name="#) Local Note:\t"; M=1; long=0; }; -- M=1; for OBJEct Media record tag in doObjNote() for GFT -- V1.9 NOTE = { Name="#) Note:\t\t"; T=1; M=1; N=1; long=0; }; -- M=1; for OBJEct Media record tag in doObjNote() for GFT -- V1.9 CONC = { Name=""; T=1; M=1; N=1; X=1; long=0; }; -- N=1; Rid initial for Note record to keep CONT = { Name="\t\t"; T=1; M=1; N=1; X=1; long=0; }; -- X=1; for kept tags to eXclude from local Note TRLR = { Name="#) Trailer:\t"; }; __LIVING = { Name="#) Living:\t"; }; -- V4.0 Record Flags __PRIVATE = { Name="#) Private:\t"; }; -- V4.0 __TENTATIVE= { Name="#) Tentative:\t"; }; -- V4.0 Fact Flags __REJECTED = { Name="#) Rejected:\t"; }; -- V4.0 __PREFERRED= { Name="#) Preferred:\t"; }; -- V4.0 } local function doNameTag(strTag) -- Lookup dicNameTag dictionary above -- V1.8 FTL -- strTag ~ Tag of current line if not dicNameTag[strTag] then dicNameTag[strTag] = { Name="#) "..strTag..":\t"; } -- Needed for UDF and Record Flags, etc -- V4.0 end return dicNameTag[strTag] end -- local function doNameTag local function getRidName(strVal) -- Convert @Rid@ to [rid] + Record Name -- V1.9 -- strVal ~ Value of current line local strPre, strRid = strVal:match("^( ?)@(%w+)@$") if strRid then strVal = strPre..strRecordText(strRid) -- V2.2 end return strVal end -- local function getRidName local function doMakeNote(arrNote,strNote,intLev,strTag,strVal) -- Make local note line -- V1.8 FTL -- V2.5 LFT -- arrNote ~ List of Note lines -- strNote ~ Tag for Note line -- intLev ~ Level of current line -- strTag ~ Tag for current line -- strVal ~ Value of current line local dicRule = dicRule["1 CHAN"] if dicRule and dicRule.Item:match("^Remove") then -- CHANge timestamps need removing local strData = tostring(intLev)..strTag if strData == "1CHAN" or strData == "2DATE" or strData == "3TIME" then return end end local strName = doNameTag(strTag).Name:gsub("^#",intLev) -- Name according to dictionary above -- V2.3 cater for UDF tag if strTag == "CONC" then -- Change CONCatenation to simple unlabelled CONC strNote = strNote:gsub("CONT","CONC") end table.insert(arrNote,strNote..strName..getRidName(strVal)) -- Save local note name tag line -- V1.9 end -- local function doMakeNote local function doLocalNote(arrNote,intLine) -- Insert local Note at end of Record/Fact -- V1.8 FTL -- arrNote ~ List of Note lines -- intLine ~ Line number to insert Note for intNote, strNote in ipairs (arrNote) do doInsert(strNote,intLine) intLine = intLine + 1 end return intLine end -- local function doLocalNote local function doConJoin(tblRecord,tblLineNo) -- Merge CONT & CONC lines into one long root line -- V4.0 -- tblRecord ~ Record of lines to process -- tblLineNo ~ Associated line numbers local intLine = 0 -- Current record line index local strLong = "" -- Accumulating long root line repeat intLine = intLine + 1 local strLine = tblRecord[intLine] -- Get next line from record local intLev, strTag, strVal = getParts(strLine) if strTag == "CONT" then table.remove(tblRecord,intLine) -- Delete line from record array table.remove(tblLineNo,intLine) -- Delete import line number to keep arrays in step intLine = intLine - 1 strLong = strLong.."\r"..strVal -- Append CONTinuation text after return tblRecord[intLine] = strLong elseif strTag == "CONC" or strTag == "_TEXT" then -- V4.3 table.remove(tblRecord,intLine) -- Delete line from record array table.remove(tblLineNo,intLine) -- Delete import line number to keep arrays in step intLine = intLine - 1 strLong = strLong..strVal -- Apend CONCatenation or _TEXT text tblRecord[intLine] = strLong elseif strTag == "_FMT" and strRich ~= "RETAIN" then -- V4.4 -- Except for RETAIN table.remove(tblRecord,intLine) -- Delete line from record array table.remove(tblLineNo,intLine) -- Delete import line number to keep arrays in step intLine = intLine - 1 strLong = strLong.."\n_FMT" -- Apend _FMT tag -- V4.3 tblRecord[intLine] = strLong else strLong = strLine -- Initial long text line end until intLine >= #tblRecord end -- local function doConJoin local function doDisJoin(tblRecord) -- Break one long root line into CONT & CONC lines -- V4.0 -- tblRecord ~ Record of lines to process local intLine = 1 -- Current record line index repeat local strLine = tblRecord[intLine] -- Get next line from record local intLev, strTag, strVal = getParts(strLine) local strLev = tostring(intLev) if ( doNameTag(strTag).long or strTag:match("@N%d+@") ) and ( #strLine > 255 or strVal:match("\r") ) then -- This is a long text line to be split into CONT/CONC lines strTag = " "..strTag.." " table.remove(tblRecord,intLine) for strText in strVal:gmatch("([^\r]*)") do -- Split into separate CONT lines local isFirst = true repeat local intMax = 254 - #strTag - 2 -- Max = 254 - length of tag - level digit & space while strText:sub(intMax,intMax+1):match(" ") do intMax = intMax - 1 -- Avoid CONC break on space end if intMax <= 0 then intMax = 254 - #strTag - 2 end -- Cater for excessive 250+ consecutive spaces -- V4.5 local strVal = strText:sub(1,intMax) if isFirst then -- Create original line or subsequent CONT line if #strVal == 0 then strVal = " " end -- Ensure CONT has at least one character for strict GEDCOM table.insert(tblRecord,intLine,strLev..strTag..strVal) isFirst = false if not strTag:match(" CON[CT] ") then strLev = tostring(intLev+1) -- Incremented level for all subsequent lines end strTag = " CONT " else -- Create subsequent CONC line table.insert(tblRecord,intLine,strLev.." CONC "..strVal) end intLine = intLine + 1 strText = strText:sub(intMax+1) -- Extract tail of long text until #strText == 0 end else intLine = intLine + 1 end until intLine > #tblRecord end -- local function doDisJoin local function doPrune(strVal) -- Prune invalid chars and @@ and [[privacy]] and convert rich text -- strVal ~ Any text value or GEDCOM line to be pruned if strAbbr == "ANC" then strVal = strVal:gsub("/%.%.","|..") -- Special fix for Ancestry that rejects /../../ -- V4.0 end local intLev, strTag, strAny, isLink = getParts(strVal) if strAtAt ~= "@@" then -- Some programs do not honour GEDCOM "@@" convention -- V3.3 strAtAt if strAtAt ~= "@" and ( isLink or strTag:match("^_?FILE$") or strTag:match("^_?EMAIL$") ) then strVal = strVal:gsub("@@","@") -- "@@" becomes "@" in UDF link or Filename or Email address -- V1.9 -- V4.0 else strVal = strVal:gsub("@@",strAtAt) -- "@@" becomes "@", "", etc -- V1.9 -- V3.3 strAtAt replaces isPrune end end if strPriv == "INCLUDE" then -- Remove [[private & #hash tag text ]] brackets -- V3.2 -- V4.0 strVal = strVal:gsub("%[%[(.-)%]%]","%1") elseif strPriv == "EXCLUDE" then -- Exclude all the [[private & #hash tag text]] -- V3.2 -- V4.0 strVal = strVal:gsub("%[%[.-%]%]","") end if strVal:match("\n_FMT$") then -- Formatted rich text line -- V4.0 -- V4.3 strVal = strVal:match("^(.*)\n_FMT$") -- -- 2 _LINK_I @I6@ 3 _LKID 1 2 _LINK_E @E1@ 3 _LKID 3 -- \r A | B | C \r 1 | 2 | 3 \r -- * Bullet >> Indents 2 SOUR @S7@ -- FH V6 will allow similar and other HTML such as and anchor tags if strRich == "REMOVE" then -- Remove any rich text format codes -- V4.0 -- Web Link or --# strVal = strVal:gsub('','Weblink: "%1" %2') strVal = strVal:gsub('','Weblink: %1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 strVal = strVal:gsub('','Weblink: %1 "%2"') -- Link to Record strVal = strVal:gsub('','Record: %2') -- >> Indents to tab strVal = strVal:gsub('^>+','\t'):gsub('\r>+','\r\t') -- bold italic underline strike subscript superscript etc deleted strVal = strVal:gsub('','') elseif strRich == "REDUCE" then -- Reduce rich text to basic GedStar Pro & RootsMagic format -- V4.0 -- Web Link or --# strVal = strVal:gsub('','Weblink: "%1" %2') strVal = strVal:gsub('','Weblink: %1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 strVal = strVal:gsub('','Weblink: %1 "%2"') -- Link to Record strVal = strVal:gsub('','Record: %2') -- bold italic underline strike strVal = strVal:gsub('<(/?[bius])>',StrLessTagMore) -- => ? so < and > are hidden -- Source Citation to superscript strVal = strVal:gsub('','%1') -- subscript superscript strVal = strVal:gsub('<(/?su[bp])>',StrLessTagMore) -- => ? so < and > are hidden -- etc... strVal = strVal:gsub('','') -- deleted -- * Bullet strVal = strVal:gsub('^%* ','\t'..StrBullet..' '):gsub('\r%* ','\r\t'..StrBullet..' ') -- >> Indents to tab strVal = strVal:gsub('^>+','\t'):gsub('\r>+','\r\t') if strAbbr == "GSP" then -- Convert anchor to ? so < and > are hidden strVal = strVal:gsub('<(a href=.-)>',StrLessTagMore):gsub('<(/a)>',StrLessTagMore) strVal = strVal:gsub('<(.-)>','<%1>') -- Convert chars < > to HTML codes end strVal = strVal:gsub(StrLessLess..'(.-)'..StrMoreMore,'<%1>')-- Convert ? => elseif strRich == "LEGACY" then -- Convert rich text to Legacy 9 format -- V4.0 -- Web Link or --# strVal = strVal:gsub('','Weblink: "%1" %2') strVal = strVal:gsub('','Weblink: %1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 strVal = strVal:gsub('','Weblink: %1 "%2"') -- Link to Record strVal = strVal:gsub('','Record: %2') -- bold italic underline strVal = strVal:gsub('<(/?[biu])>',StrLessTagMore) -- => ? -- Source Citation to superscript strVal = strVal:gsub('','%1') -- subscript superscript strVal = strVal:gsub('<(/?su[bp])>',StrLessTagMore) -- => ? -- Strikeout etc... strVal = strVal:gsub('',''):gsub('','') -- deleted -- * Bullet strVal = strVal:gsub('^%* ','\t'..StrBullet..' '):gsub('\r%* ','\r\t'..StrBullet..' ') -- >> Indents to tab strVal = strVal:gsub('^>+','\t'):gsub('\r>+','\r\t') elseif strRich == "HTML 5" then -- Convert rich text to HTML 5 for FMP, MyHeritage, RootsFinder, TNG, TribalPages ZoomPast -- V4.0 -- V4.2 local _, _, strAny = getParts(strVal) strAny = strAny:gsub( '&' , '&' ) -- Reserved characters -- V4.3 strAny = strAny:gsub( '`' , '`') strAny = strAny:gsub( '"' , '`' ) -- Temporarily substitute " with ` so any residual ` can become " strAny = strAny:gsub( "'" , ''' ) strAny = strAny:gsub( '\\<', '<' ) -- Rich Text escaped characters strAny = strAny:gsub( '\\>', '>' ) strAny = strAny:gsub( '\\%*','*' ) -- Some products do not allow these HTML codes so get converted back later strAny = strAny:gsub( '\\|', '|' ) strAny = strAny:gsub( '\\\\','\' ) -- bold italic underline strikeout retained strAny = strAny:gsub('(<[bius]>)', '%1') -- Text Colour not in matched pairs strAny = strAny:gsub('', '') strAny = strAny:gsub('(.-)', '%2') strAny = strAny:gsub('', '') -- Highlight Colour not in matched pairs strAny = strAny:gsub('', '') strAny = strAny:gsub('(.-)', '%2') strAny = strAny:gsub('', '') -- #Hashtag [[#Text]] strAny = strAny:gsub('%[%[(#.-)]]', '%1') -- Table \r A | B | C \r 1 | 2 | 3 \r\r strAny = strAny:gsub('\r', function(allCol) local strCol = "" for oneCol in allCol:gmatch("%d+") do oneCol = math.floor( tonumber(oneCol) / 10 ) oneCol = string.format("%d",oneCol) strCol = strCol..'' -- V4.3 -- Added end return ''..strCol end ) strAny = strAny:gsub('(.-)\r', function(strRow) return '' end) -- V4.3 -- Add

-- Alignment strAny = strAny:gsub('', '') -- V4.3 -- Removed span strAny = strAny:gsub('', '') -- V4.3 -- Removed span strAny = strAny:gsub('', '') -- V4.3 -- Removed span strAny = strAny:gsub('

%s-(.-)

', '

%1

') -- V4.3 strAny = strAny:gsub('

%s-(.-)

', '

%1

') strAny = strAny:gsub('

%s-(.-)

', '

%1

') strAny = strAny:gsub('(.-)\r', '%1\r') -- V4.3 strAny = strAny:gsub('(.-)\r', '%1\r') strAny = strAny:gsub('(.-)\r', '%1\r') -- Font Size not in matched pairs and need adjusting relative to intFontSize from Registry -- V4.5 strAny = strAny:gsub('', function(strSize) return '' end ) strAny = strAny:gsub('', function(strSize) return '' end ) strAny = strAny:gsub('', '') strAny = strAny:gsub('(.-)', '%2') strAny = strAny:gsub('', '') -- Font Style not in matched pairs and need all " string quotes and trailing digits removing strAny = strAny:gsub('', function(strFont) return ( strFont:gsub('`',''):gsub(',+%d->$','>') ) end ) strAny = strAny:gsub('', '') strAny = strAny:gsub('(.-)', '%2') strAny = strAny:gsub('', '') -- Web Link or strAny = strAny:gsub('', '%2') -- Link to Record strAny = strAny:gsub('', 'Record: "%2"') -- * Bullet strAny = strAny:gsub('^%* ', '> '..StrBullet..' ') strAny = strAny:gsub('\r%* ', '\r> '..StrBullet..' ') -- >> Indents strAny = strAny:gsub('^(>+)', function(strEm) return '\1'..tostring(#strEm * 2)..'\1' end) strAny = strAny:gsub('\r(>+)', function(strEm) return '\r\1'..tostring(#strEm * 2)..'\1' end) strAny = strAny:gsub('\1(%d+)\1(.-)\r', '%2\r') -- Source Citation strAny = strAny:gsub('', '%1') -- Characters and Paragraphs strAny = strAny:gsub(StrBullet , '•' ) strAny = strAny:gsub( '`' , '"' ) -- Initially " became ` so any left now become " -- V4.3 strAny = strAny:gsub( '`', '`' ) -- Products MYH & RFT do not allow such HTML codes -- V4.3 strAny = strAny:gsub( '*' , '*' ) strAny = strAny:gsub( '|' , '|' ) strAny = strAny:gsub( '\' , '\\' ) -- ? -- Maybe

...

or
...
but without \r would work? -- # strAny = strAny:gsub('([^\r]*)(\r?)','%1
%2') --
double spaces lines in products that accept HTML -- # strAny = strAny:gsub('([^\r]*)(\r?)','
%1
%2') --
...
double spaces lines in products that accept HTML -- # strAny = strAny:gsub('([^\r]*)(\r?)','

%1

%2') --

...

double spaces lines in products that accept HTML -- # strAny = strAny:gsub('

','

 

') strVal = tostring(intLev).." "..strTag.." "..strAny end elseif strAbbr == "GSP" and dicNameTag[strTag] and dicNameTag[strTag].A then -- and LFT?? strVal = strVal:gsub('<(.-)>','<%1>') -- Convert attribute value reserved chars < > to HTML escape codes end if strFull == "-" then -- # strVal = strVal:gsub("[%z\01-\31\127]"," ") -- In Brief mode replace invalid control chars with space -- V4.0 %z not allowed in Lua 5.3 strVal = strVal:gsub("[\01-\12\14-\31\127]"," ") -- In Brief mode replace invalid control chars with space -- V4.0 \00 highly unlikely; \13 = \r must be kept end strVal = strVal:gsub("^([23] AGE [<>]) +","%1") -- Remove invalid AGE and head & tail space chars -- V4.0 strVal = strVal:gsub("^ *(%d [_%u]+) *","%1&") -- Preserve one space after tag even if on tail -- V4.0 strVal = strVal:gsub("^ *(.-) *$","%1") -- :gsub(" +$","") -- V4.0 strVal = strVal:gsub("^(%d [_%u]+)&","%1 ") return strVal end -- local function doPrune --[[ In the following functions certain conventions apply: The function argument(s) are the capture(s) in the Old Tag value of the Rule. Use doInsert() & doModify() & doDelete() to insert & modify & delete one line either above or below current line. -- V1.8 Returning "" will delete current tag line, and nil will retain current tag line unchanged. --]] local function doRemoveAll(strTop,intLine) -- Remove all subordinate tag lines; used by doObjectId() and doObjRecord() and doUnnamed() -- strTop ~ GEDCOM level number of parent -- intLine ~ Record line number of 1st child local intTop = tonumber(strTop:match("^(%d+)")) local intNext = (intLine or tblRecord.Line)+1 while intNext <= #tblRecord do -- #tblRecord reduces as lines removed -- V1.8 local intLev = getParts(tblRecord[intNext]) if intLev <= intTop then break end -- End when top line level reached doDelete(intNext) -- Delete subordinate line -- V1.8 end return "" -- Remove original line end -- local function doRemoveAll local function doUnnamed() -- Give [unnamed person] a Surname -- V3.7 if isRecord(tblRecord.Arg) then return nil end -- V3.8 local strLine = doRemoveAll("1") -- Only applies where 1 CHAN is erased if isRecord("INDI") and not tblRecord.Name then -- No NAME instances so set Surname /?/ especially for (RMT) Roots Magic Tree bug strLine = "1 NAME /?/" end return strLine end -- local function doUnnamed local function doCharCode(strText) -- Set character encoding and insert any mandatory tags -- strText ~ Current encoding (not used) for _, strLine in ipairs (tblRecord.Arg or {}) do -- V3.9 if strLine == "2 FORM " then local arrParts = {} for intPart = 1, intParts do -- Compose "2 FORM jur1, jur2, jur3" etc depending on max Place parts -- V3.8 AGS table.insert(arrParts,"jur"..tostring(intPart)) end strLine = strLine..table.concat(arrParts,", ") end doInsert(strLine,tblRecord.Line) -- Insert mandatory tags -- V3.8 AGS end -- # local strChar = TblChar[TblOption.Char].Abbr -- V4.0 moved to SetSharedData() if strChar == "UTF16" then return "1 CHAR UNICODE" -- Character encoding for UTF-16 -- V1.8 elseif strChar == "UTF8" then return "1 CHAR UTF-8" -- Character encoding for UTF-8 else return "1 CHAR ANSI" -- Otherwise encoding for ANSI (and ISO) end end -- local function doCharCode local function strTidyText(strText) -- Remove redundant middle, leading, and trailing commas -- V2.2 -- strText ~ Place or Address text to be tidied strText = (strText or ""):gsub("\r"," ") -- V4.0 if not isTidy then return strText end -- Is tidying disabled? -- V3.3 local newText = dicTidy[strText] -- See doAnalyse() if not newText then newText = strText:gsub(", ?, ?",", "):gsub(", ?, ?",", ") newText = newText:gsub("^[, ]+",""):gsub("[, ]+$","") newText = newText:gsub(" +"," "):gsub(" ,",","):gsub(",([^ ])",", %1") -- Remove multiple spaces & ensure comma is followed by space -- V3.3 -- V4.5 dicTidy[strText] = newText --[=[ local strTidy = fhCallBuiltInFunction("TextPart",strText,1,0,"TIDY"):gsub(" +"," "):gsub(" ,",",") -- V4.5 if newText ~= strTidy then fhMessageBox("\nTidy text Plugin and TextPart function differ:\n" .. newText .. "\n" .. strTidy .. "\n") end --]=] end return newText end -- local function strTidyText local function doPutOther(strLev,strVal) -- Put E-mail/Website/etc field in another field; used by doRecNote() and doAddr2Plac() -- strLev ~ GEDCOM level number -- strVal ~ GEDCOM value text local intLev = tonumber(strLev) local arrArg = tblRecord.Arg:split() local strArg = arrArg[1] local intArg = tonumber(arrArg[2]) or -1 -- V3.1 Arg is "NICK,1" for "_USED" local intEnd = #tblRecord if intArg == -1 then intEnd = 1 end for intPrev = tblRecord.Line + intArg, intEnd, intArg do -- Search previous/subsequent lines in Record buffer local strPrev = tblRecord[intPrev] local intPrv, strTag = getParts(strPrev) if strTag == strArg then doModify(strPrev..", "..strVal,intPrev) -- Append E-mail/Website/Address, etc details to existing field value -- V4.0 return "" -- Delete original line end if intPrv < intLev then break end -- Stop search at previous level 1 Fact tag or start of record end return strLev.." "..strArg.." "..strVal -- Convert E-mail/Website/etc tag to other tag end -- local function doPutOther local function doContacts(strVal) -- Ensure 2 ADDR is before 2 EMAIL/WWW/PHON in Standard GEDCOM, Legacy, etc -- V3.3 -- strVal ~ GEDCOM value text local strTag = tblRecord.Arg -- Alternative tag required local intLine = tblRecord.Line for intPrev = intLine-1, 1, -1 do -- Search previous lines for ADDR in Record local intLev, strTag = getParts(tblRecord[intPrev]) if strTag == "ADDR" then -- ADDR found, so already exists return tblRecord.Arg..strVal -- Convert to required EMAIL/WWW/PHON tag -- V4.0 end if intLev < 2 then -- ADDR missing, so insert empty ADDR tag that should always be OK doInsert("2 ADDR ",intLine) doInsert(tblRecord.Arg..strVal,intLine+1) -- Convert to required EMAIL/WWW/PHON tag -- V4.0 return "" end end end -- local function doContacts local function findLongText(intLine,intEnd,strNeed,strElse) -- Find earliest local NOTE/TEXT above or below current line -- V1.8 -- intLine ~ Line number to start search -- intEnd ~ Level to end search -- strNeed ~ Find this tag -- strElse ~ Else this tag for intPrev = intLine, 1, -1 do -- Search previous lines in Record buffer to start of section (was intLine-1 but failed for doStandAttr()) local intLev = getParts(tblRecord[intPrev]) if intLev < intEnd then -- End searching previous lines at next level up for intNext = intPrev+1, #tblRecord+1 do -- Search beyond Record in case no CHAN tag, and substitute dummy "0 END" tag local strNext = tblRecord[intNext] or "0 END" -- Find tag or next level up to end section local intLev, strTag, strAny, isLink = getParts(strNext) strTag = intLev.." "..strTag if not isLink and -- Exclude any Record @link@ or UDF @@link@@ -- in V1.8 is this needed??? ( strTag == strNeed or strTag == strElse ) or intLev < intEnd then return intNext, strTag -- Return line and tag found end end end end return #tblRecord+1," " -- Should not get here end -- local function findLongText local dicAnyNote = {NOTE=1;_NOTE=1;PLAC=1;ADDR=1;TEXT=1;REFN=1;SOUR=1;DESC=1;} -- Tags whose CONC/T/subsidiary tags must move to local NOTE/TEXT -- V4.0 add DESC for _SRCT local dicAnyCont = { CONC=1; CONT=1; } -- Tags that CONCatenate or CONTinue a local NOTE/TEXT local function addNoteText(strVal,strNeed,strElse,intLine) -- Add NOTE & CONC/T to existing NOTE/TEXT or to end of section -- V1.8 appends to existing Note rather than prepend -- Values for doRecNote strVal,"1 NOTE","1 CHAN",intLine -- Values for doFactNote strVal,"2 NOTE","2 _PLAC",intLine -- tblRecord.Line or intLine must point to line holding dicAnyNote{} tags to find CONC/T/subsidiary tags -- Values for doLocNote strVal,"%d NOTE" -- Values for doCitation strVal,"%d DATA","%d+1 TEXT",intLine -- strVal ~ Value of current tag -- strNeed ~ Find this tag -- strElse ~ Else this tag -- intLine ~ Line number local arrSucc = strVal:split("\n") -- Split off any successor lines to NOTE/TEXT -- V1.8 strVal = arrSucc[1] or "" table.remove(arrSucc,1) local intEnd = tonumber(strNeed:match("^(%d) ")) -- Section end is when this level exceeded local intPut = intEnd + 1 -- Put CONC/T tags into this level if strNeed:match("CONT") then intPut = intEnd end strElse = strElse or "0 ZZZZ" -- Add NOTE/TEXT after existing NOTE/TEXT or before this other tag -- V1.8 intLine = intLine or tblRecord.Line local intNext, strTag = findLongText(intLine,intEnd,strNeed,strElse) if strNeed:match("^%d DATA$") then -- Need DATA & TEXT rather than NOTE if strTag ~= strNeed then doInsert(strNeed,intNext) end -- DATA tag not found so insert one strNeed = strElse -- Now need TEXT tag at next level down intEnd = intEnd + 1 intPut = intPut + 1 intNext, strTag = findLongText(intNext,intEnd,strNeed,"0 END") end local strArg = tblRecord.Arg or "" -- V3.3 if strTag == strNeed then if dicWhere[strArg] == StrBefore then -- Insert note before existing NOTE/TEXT -- V3.3 local intLev, _, strVal = getParts(tblRecord[intNext]) -- So it needs to become a CONTinuation -- V3.3 doModify(tostring(intLev+1).." CONT "..strVal,intNext) -- V4.0 else -- Insert note after existing NOTE/TEXT -- V3.3 repeat intNext = intNext + 1 -- Find end of local NOTE/TEXT local intLev, strTag = getParts(tblRecord[intNext] or strElse) until intLev < intPut or not dicAnyCont[strTag] strNeed = intPut.." CONT" -- NOTE/TEXT already exists so use CONTinuation -- V1.8 end end local strLab = dicLabel[strArg] or strArg -- V3.3 doInsert(strNeed.." "..strLab..strVal,intNext) -- Insert new local NOTE/TEXT/CONT with label -- V1.8 if intNext <= intLine then intLine = intLine + 1 end local intLev, strTag = getParts(tblRecord[intLine]) local intGet = intLev + 1 local intCont = intLine + 1 -- Search next lines for CONC/CONT/subsidiary tags if dicAnyNote[strTag] then -- Move any CONC/CONT/subsidiary tags below this level to NOTE/TEXT intNext = intNext + 1 while intCont <= #tblRecord do -- #tblRecord reduces as lines removed local strCont = tblRecord[intCont] local intLev, strTag, strVal = getParts(strCont) if intLev < intGet then break end -- Escape when no more CONC/CONT/subsidiary tag if strTag == "CONC" then strCont = intPut.." CONC "..strVal elseif strTag == "CONT" then strCont = intPut.." CONT "..strLab..strVal else strCont = intPut.." CONT "..strLab..intLev.." "..strTag.."\t"..strVal end doInsert(strCont,intNext) -- Move associated CONC/CONT/subsidiary lines to keep with new NOTE/TEXT if intNext < intCont then intNext = intNext + 1 intCont = intCont + 1 end doDelete(intCont) -- Delete original line -- V1.8 end end intCont = intCont - 1 if intCont ~= tblRecord.Line then doDelete(intCont) end -- Delete original NOTE line if not current line for intSucc, strSucc in ipairs (arrSucc) do -- Insert successor lines to NOTE/TEXT -- V1.8 intNext = intNext + 1 -- Used by doWitness() to add SOURces and avoid them appearing amongst CONC/T doInsert(strSucc,intNext) end return end -- local function addNoteText local function doFactNote(strVal,intLine) -- Convert various text lines to a Fact local Note; used by doCustAttr(), doAssociate(), doWitness() addNoteText(strVal,"2 NOTE","2 _PLAC",intLine) -- Add the Note to existing Note or before To/From Place tag or end of Fact return "" -- Delete original line end -- local function doFactNote local function doFactType(strVal) -- Convert TYPE line to a Fact local Note unless custom EVENt for intPrev = tblRecord.Line-1, 1, -1 do -- Search previous lines in Record buffer for a Fact local intLev, strTag = getParts(tblRecord[intPrev]) if intLev == 2 and strTag == "TYPE" then break end -- Synthesised custom EVENt/FACT if intLev == 1 then if strTag == "EVEN" or strTag == "FACT" -- V3.8 or strTag == "REFN" then return nil end -- Retain EVENt/FACT/REFN Custom Id TYPE line break end end if tblRecord.Arg then doFactNote(strVal) end -- V2.2 -- V3.8 return "" -- Delete original line end -- local function doFactType local function doAssoType(strVal) -- Remove 2 TYPE from 1 ASSO because invalid GEDCOM -- V3.8 -- strVal ~ TYPE value if tblRecord[tblRecord.Line-1]:match("1 ASSO") then return "" -- Delete original line end return nil -- Retain original line end -- local function doAssoType local function doRecNote(strVal,intLine) -- Convert various text lines to Record local Note; used by doLocNote(), doRepoNote(), doRepoLink(), doRecFlags(), doIndiName(), doPedigree(), doShortTitl(), doCustomId(), doSrcTemp*() -- V4.0 add intLine -- strVal ~ Value of current line -- intLine ~ Line number of line or nil local strArg = tblRecord.Arg if strArg == "Postal Address" then -- V3.3 strVal = strTidyText(strVal) end if strArg == "Picture Note" and not isRecord("OBJE") then -- Retain original line if not Media -- V4.4 return nil end if isRecord("SUBM, SUBN") then -- Submitter/Submission records have no Note -- V1.8 if strArg:match(" Address$") then tblRecord.Arg = "PHON" -- For E-mail/Web Address use Phone -- V1.8 return doPutOther("1",strVal) end return nil -- Retain original line end local strNeed = "1 NOTE" if tblRecord.Name and strArg:match("^~.*[Nn]ame") then -- Only applies to INDIvidual records local strName = tblRecord.Name..strArg -- Add Name instance number to label -- V1.8 -- V3.3 if not dicLabel[strName] then dicLabel[strName] = tblRecord.Name..(dicLabel[strArg] or strArg) dicWhere[strName] = dicWhere[strArg] end tblRecord.Arg = strName -- Add Name instance number to label -- V1.8 -- V3.3 elseif isRecord("NOTE, _RNOT") then strNeed = "1 CONT" -- Note Record is special case -- V1.8 end addNoteText(strVal,strNeed,"1 CHAN",intLine) -- Add Note to existing Note or insert before Change tag -- V4.0 add intLine return "" -- Delete original line end -- local function doRecNote local function doLocNote(strLev,strVal) -- Convert mainly Media Object lines to its local Note; used by doLocDate() -- strLev ~ Level of current line -- strVal ~ Value of current line if strLev == "1" then return doRecNote(strVal) end -- V2.0 addNoteText(strVal,strLev.." NOTE") -- Add the Note before existing Note tag or end of level return "" -- Delete original line end -- local function doLocNote local function doLoc_Nota(strLev,strVal) -- Handle _NOTA annotations -- V4.0 -- strLev ~ Level of current line -- strVal ~ Value of current line if isRecord("OBJE") then return doLocNote(strLev,strVal) -- Within a Media record treat as Picture Note end return doRemoveAll(strLev) -- Otherwise remove entirely end -- local function doLoc_Nota local function doNoteLink(strLev,strVal) -- Note Record link id conversion for Legacy tabs Research and Medical -- V2.6 -- V3.3 -- strLev ~ Level of current line -- strVal ~ Value of current line local strRid = strVal:match("^@@?(%u%d+)@@?$") -- V3.3 if strRid then local strNid = dicNote[strRid] if strNid then tblRecord.Title = "Note Record Link" return strLev.." NOTE @"..strNid.."@" end end return nil -- Retain original line end -- local function doNoteLink local function doName2Note(strLev,strVal) -- Move Phonetic or Romanised name to local Note -- V4.0 -- strLev ~ Level of current line -- strVal ~ Value of current line local intLine = tblRecord.Line + 1 local strLine = tblRecord[intLine] local strType = strLine:match("^%d TYPE (.*)") if strType then strVal = strVal.." Type: "..strType -- Append the Type description doDelete(intLine) end return doLocNote(strLev,strVal) -- Create local Note end -- local function doName2Note local function doFlagNote(strLev) -- Move Fact Flags to local Note -- V4.0 -- strLev ~ Level of current line local strVal = "" local intLine = tblRecord.Line + 1 while intLine <= #tblRecord do -- Append the Flags local strLine = tblRecord[intLine] local strFlag = strLine:match("^3 __%u+ (.*)") -- 3 __PRIVATE Private if strFlag then -- 3 __REJECTED Rejected strVal = strVal..strFlag.." " -- 3 __TENTATIVE Tentative doDelete(intLine) -- 3 __PREFERRED Preferred else break end end return doLocNote(strLev,strVal) -- Create local Note end -- local function doFlagNote local function doObjRecord(strIni,strRid) -- Adjust Link/Note/CONC/CONT captions, or delete unused OBJE record -- V3.3 -- strIni ~ Initial letter of record id -- strRid ~ Numerical part of record id local strKey = "0O"..strRid -- Use the Sid=0 & Rid key to lookup Media Object details local tblObje = dicObje[strKey] if not tblObje then return nil end -- Skip artificial part frame OBJE record if tblObje.Used then local strArg = tblRecord.Arg -- Media Object Record is in use if type(strArg) == "table" then strIni = strArg[2] strArg = strArg[1] end local strLab = dicLabel[strArg] or strArg.."\t" -- "Caption Note:" label local arrLine = {} -- List of lines comprising one caption local arrCapt = {} -- List of multiple Link/Note captions local isLine1 = true -- True for first Caption Note line local intNote = false -- Line where Caption Note is inserted local intLine = tblRecord.Line + 1 local function addCaption(arrLine,arrCapt) -- Conditionally add Caption Note -- V4.1 if #arrLine > 0 then local isKeep = true if isCapt then -- Does any saved Caption Note match this Caption Note for intCapt, arrCapt in ipairs (arrCapt) do if table.concat(arrCapt,"\n") == table.concat(arrLine,"\n") then isKeep = false -- Match found so do not keep this Caption Note break end end end if isKeep then -- Keep this Caption Note table.insert(arrCapt,arrLine) for intLine, strLine in ipairs (arrLine) do if isLine1 then -- First line needs "1 NOTE" tag strLine = strLine:gsub("^2 CONT","1 NOTE") isLine1 = false end intNote = intNote + 1 doInsert(strLine,intNote) -- Insert the Caption Note line end end end end -- local function addCaption repeat -- Search for Link/Note captions local strLine = tblRecord[intLine] local intLev, strTag, strVal, isLink = getParts(strLine) if intLev == 1 and ( strTag == "NOTE" or strTag == "_NOTA" ) and not isLink then -- V4.1 if isCaption then -- Collect all Link/Note/CONC/CONT caption lines together but remove blank ones strVal = strLab..strVal -- For FULL~ABS and FILE~REL and FILE~ABS if not intNote then intNote = intLine - 1 -- Remember where Caption Note is inserted end arrLine = {} if strVal ~= strLab or tblRecord[intLine+1]:match("2 SOUR") -- Exclude blank line unless has a Citation or Continuation or tblRecord[intLine+1]:match("2 CON[CT]") then table.insert(arrLine,"2 CONT "..strVal) -- Save the Link/Note caption line end doDelete(intLine) -- Delete original Link/Note caption repeat strLine = tblRecord[intLine] intLev, strTag, strVal = getParts(strLine) -- Search for CONC/CONT continuation lines if intLev == 2 and strTag:match("CON[CT]") then if strTag == "CONT" then strTag = "2 CONT " strVal = strLab..strVal else strTag = "2 CONC " end table.insert(arrLine,strTag..strVal) -- Save the CONC/CONT caption line doDelete(intLine) -- Delete original CONC/CONT caption else intLine = intLine + 1 end until not strTag:match("2 CON[CT]") addCaption(arrLine,arrCapt) -- V4.1 else doRemoveAll("1",intLine) -- Remove NOTE caption and subsidiary _CONC/CONT/_ASID/_AREA, etc doDelete(intLine) -- For PART~ABS and ALL~ABS end else intLine = intLine + 1 end until intLine >= #tblRecord if IntFhVersion > 6 and isCaption then -- V4.1 for intSid = 1, 99 do local strKey = tostring(intSid).."O"..strRid -- Check for _ASID Link/Notes local tblObje = dicObje[strKey] if tblObje then -- Synthesise captions from tblObje arrLine = {} for strTag, arrTag in pairs (tblObje.ASID) do -- Insert Link/Note tags -- # local intLev = arrTag[1].Lev local strTag = arrTag[1].Tag local strVal = arrTag[1].Val if strTag == "NOTE" then strVal = strLab..strVal -- For FULL~ABS and FILE~REL and FILE~ABS if not intNote then intNote = tblRecord.Line -- Determine where Caption Note is inserted end table.insert(arrLine,"2 CONT "..strVal) -- Save the CONT caption line addCaption(arrLine,arrCapt) -- V4.1 end end end end end return "0 @"..strIni..strRid.."@ OBJE" -- Adjust original line end doRemoveAll("0",1) -- Unused record to be deleted ~ doObjectId() and doAnalyse() set tblObje.Used other records use of %d OBJE @O123@ taking account of _AREA & NOTE return "" -- Delete original line --# return nil -- Retain original line (for testing only) end -- local function doObjRecord local function doObjRec2FH(strRid) -- Revert to FH V5/6 style, or delete unused OBJE record -- V4.0 -- strRid ~ Record id local strKey = "0"..strRid -- Use the Sid=0 & Rid key to lookup Media Object details local tblObje = dicObje[strKey] or { OBJE={}; } local strNote = "1 _NOTE" local intLine = tblRecord.Line + 1 while intLine < #tblRecord do local strLine = tblRecord[intLine] local intLev, strTag, strVal, isLink = getParts(strLine) strTag = strTag:upper() local arrTag = tblObje.OBJE[strTag] if arrTag then -- Check record level 1 tags if strTag == "CHAN" then for intSid = 1, 99 do local strKey = tostring(intSid)..strRid -- End of record check for _ASID Link/Notes local tblObje = dicObje[strKey] if tblObje then -- Synthesise FH V5/6 tags from tblObje local intNote = intLine doInsert("1 NOTE",intLine) intLine = intLine + 1 for strTag, arrTag in pairs (tblObje.ASID) do -- Insert Link/Note tags -- # local intLev = arrTag[1].Lev local strTag = arrTag[1].Tag local strVal = arrTag[1].Val if strTag == "NOTE" then doModify("1 NOTE "..strVal,intNote) else doInsert("2 "..strTag.." "..strVal,intLine) intLine = intLine + 1 end end end end break end local intLev = arrTag[1].Lev if intLev then doModify((strLine:gsub("^%d",intLev)),intLine) -- FORM or TITL then +1 level end elseif strTag == "NOTE" then if not isLink then doModify((strLine:gsub("^1 NOTE",strNote)),intLine) -- NOTE to _NOTE or CONT strNote = "2 CONT" end elseif strTag == "_NOTA" then if not isLink then doModify((strLine:gsub("^1 _NOTA",strNote)),intLine) -- _NOTA to _NOTE or CONT strNote = "2 CONT" else doModify((strLine:gsub("^1 _NOTA","1 NOTE")),intLine) -- _NOTA to NOTE @N99@ end elseif strTag == "_SEQ" then doDelete(intLine) -- Delete original _SEQ intLine = intLine - 1 end intLine = intLine + 1 end return nil -- Retain original line end -- local function doObjRec2FH local function doCite2Note(strLev,strVal) -- Move Note/Media/Place Source Citation to a Note -- V4.0 -- strLev ~ Level of SOUR line -- strVal ~ Value of SOUR line local intLev = tonumber(strLev) local intLine = tblRecord.Line for intPrev = intLine-1, 1, -1 do local intPre, strTag = getParts(tblRecord[intPrev]) if intPre < intLev then if strTag == "NOTE" or strTag == "OBJE" or -- Found parent Note (or Media link which should not allow a Source) ( strTag == "PLAC" and strGedExport == "5.5.1" ) then -- Found parent Place for GEDCOM 5.5.1 local strRid = strVal:match("^@(S%d+)@$") if strRid then strVal = strRecordText(strRid) end if strTag == "NOTE" then addNoteText(strVal,tostring(intLev-1).." NOTE") -- Move entire Note Source Citation into its Note if not strRid or strGedExport == "5.5.1" then return "" end -- Remove any Source tag except a Source Link for GEDCOM 5.5 else return doLocNote(strLev,strVal) -- Move entire Media/Place Source Citation into local Note end end break end end local strArg = tblRecord.Arg local strRid = strVal:match("^@(S%d+)@$") -- Link to Source Record (not Source Note) if strArg == "Copy Media" and strRid then for intObje, strObje in ipairs (dicSour[strRid] or { }) do -- Copy the Media & _LINK structure from Source to Citation for FTL/FTM -- V4.6 local intTag = getParts(strObje) strObje = strObje:gsub("^%d",tostring(intTag + intLev)) -- Adjust tag level number intLine = intLine + 1 doInsert(strObje,intLine) -- Insert Media tag line into Citation end end return nil -- Retain original line end -- local function doCite2Note local function doSourNote(strLev,strVal) -- Convert a Source Note to a synthetic Source Record -- strLev ~ Level of current line -- strVal ~ Value of current line if isRecord("HEAD") then return nil end -- Except for header record if strVal:match("^@@?S%d+@@?$") then return nil end -- Except for any record @link@ or UDF @@link@@ intSour = intSour + 1 -- Use next available Source Record Id doInsert("0 @S"..intSour.."@ SOUR") -- Create synthetic Source Record doInsert("1 TITL "..strPrefix.."Source Note: "..strVal) -- V1.8 strPrefix -- V3.3 local intLev = tonumber(strLev) local intCon = 1 local intLine = tblRecord.Line + 1 while intLine <= #tblRecord do local strLine = tblRecord[intLine] local intNxt, strTag, strVal = getParts(strLine) if intNxt <= intLev then break end if not strTag:match("^CON[CT]$") then intCon = 0 end -- V2.3 strLine = strLine:gsub("^"..intNxt,(intNxt+intCon-intLev)) -- Move subsidiary tags into Source record -- V1.8 doInsert(strLine) doDelete(intLine) end return strLev.." SOUR @S"..intSour.."@" -- Link to Source Record end -- local function doSourNote local function doSources(strLev,strVal) -- Handle Note/Witness Source and Source Note -- V2.0 -- V2.4 -- strLev ~ Level of current line -- strVal ~ Value of current line local intLev = tonumber(strLev) - 1 for intPrev = tblRecord.Line-1, 1, -1 do -- Search previous lines in Record buffer for parent tag local intPre, strTag, strAny, isLink = getParts(tblRecord[intPrev]) if intPre == 0 then strTag = strAny:match("^%u+") end -- Handle record level tag -- V3.8 if intPre == intLev then -- Found preceding parent tag for this Source Note/Citation for strArg, intArg in pairs (tblRecord.Arg) do if strTag == strArg and intLev <= intArg then -- Parent tag does not support Source Note/Citation in target product local intLine = tblRecord.Line + 1 local strPrev = "" for intPrev = intPrev-1, 1, -1 do local intP, strT = getParts(tblRecord[intPrev]) -- Obtain parent tag of that parent tag -- V3.8 if intP == intPre-1 then strPrev = strT -- SOUR and OBJE never allow Source Note/Citation break end end if isRecord("INDI, FAM") -- Individual or Family record and intLev <= 2 and intArg <= 5 -- and at Record or Fact level -- V3.8 and strPrev ~= "SOUR" and strPrev ~= "OBJE" then -- and allows Source Note/Citation -- V4.0 local intNext = intLine while intNext <= #tblRecord do local intNxt = getParts(tblRecord[intNext]) -- Find next tag at same level as parent tag if intNxt <= intLev then break end intNext = intNext + 1 end doInsert(intLev.." SOUR "..strVal,intNext) -- Insert elevated Source Note/Citation before next tag intLev = intLev + 1 while intLine <= #tblRecord do local strLine = tblRecord[intLine] -- Elevate entire Source Note/Citation before next tag local intLin = getParts(strLine) if intLin <= intLev then break end doDelete(intLine) doInsert(strLine:gsub("^%d",intLin-1),intNext) end else -- Otherwise delete entire Source Note/Citation intLev = intLev + 1 while intLine <= #tblRecord do -- Remove all subsidiary lines local strLine = tblRecord[intLine] local intLin = getParts(strLine) if intLin <= intLev then break end doDelete(intLine) end end return "" -- Delete original line end end end if intPre <= intLev then break end -- Stop search at previous level end if not strVal:match("^@S%d+@$") then return doSourNote(strLev,strVal) -- Change Source Note to synthetic Source end return nil -- Retain original line end -- local function doSources local function doEntryDate(strLev) -- Move Citation Entry Date to local Note -- V4.0 -- strLev ~ Level of current line local intLev = tonumber(strLev) local intLine = tblRecord.Line+1 local intDat, strTag, strVal = getParts(tblRecord[intLine]) if intDat-1 == intLev and strTag == "DATE" then -- Found Entry Date doDelete(intLine) doLocNote(tostring(intLev-1),strVal) end return nil -- Retain original line end -- local function doEntryDate local function doAssess(strLev,strVal) -- Convert FH V7 _QUAY Assessment to GEDCOM Standard -- V4.0 StrVal = strVal -- load(...) needs global variable assert(load([[ -- Hide the & bitwise operators in FH V6 local intVal = tonumber(StrVal) -- Find lowest assessment from _QUAY bit pattern if (intVal & 0x01) > 0 then StrVal = "0" -- Unreliable = Warning: 0x01 Unreliable elseif (intVal & 0x02) > 0 then StrVal = "1" -- Questionable = Warning: 0x02 Questionable elseif (intVal & 0x54) > 0 then StrVal = "2" -- Secondary = Evidence: 0x04 Indirect or Information: 0x10 Secondary or Source: 0x40 Derivative elseif (intVal & 0xA8) > 0 then StrVal = "3" -- Primary = Evidence: 0x08 Direct or Information: 0x20 Primary or Source: 0x80 Original else StrVal = "0" end ]]))() return strLev.." QUAY "..StrVal end -- local function doAssess local function doWitness(strWitness) -- Witness Role to Fact Note -- strWitness ~ Witness link or name local strRecId = strWitness:match("@([IF]%d+)@") -- V2.0 [IF] added for Principal Family in synthetic Fact Role if strRecId then -- Lookup Witness Individual record name strWitness = strRecordText(strRecId) -- V2.2 else strWitness = "Name:\t"..strWitness -- Named Witness name end local strRole = "\tUnknown\t " local strSour = "" local intNext = tblRecord.Line+1 while intNext <= #tblRecord do -- Search subsequent lines for Role/Source/Note local strNext = tblRecord[intNext] local intLev, strTag, strVal = getParts(strNext) if intLev <= 2 then break end -- End when Witness tags finished if strTag == "SOUR" or intLev > 3 then -- Save Source lines -- V1.9 strSour = strSour.."\n"..tblRecord[intNext] doDelete(intNext) -- Delete Source line -- V1.8 elseif strTag == "ROLE" then -- Save Role strRole = strVal.."\t" doDelete(intNext) -- Delete Role line -- V1.8 elseif strTag == "NOTE" then doFactNote(strRole..strWitness..strSour) -- Move Witness Role + Sources to Fact Note strRole = nil doFactNote(strVal,intNext) -- Move Witness Note to Fact Note elseif strTag == "_SENT" then doDelete(intNext) -- Delete Witness Sentence entirely -- V1.8 else intNext = intNext + 1 -- Should not get here -- V1.9 end end if strRole then doFactNote(strRole..strWitness..strSour) -- Move Witness Role + Sources to Fact Note end return "" -- Remove Witness line end -- local function doWitness local function doCitation(strLev,strVal) -- Process FTL/FTM & HER Citation -- V1.8 FTL/FTM -- V1.9 Heredis -- V2.2 Ancestry -- strLev ~ Level of SOUR line -- strVal ~ Value of SOUR line if isRecord("OBJE") then return "" end -- Remove from OBJEct records; HEAD, NOTE & SOUR already gone in doAnyRecord() local intLev = tonumber(strLev) local strArg = tblRecord.Arg local intLine = tblRecord.Line local strWhole = "Whole Record Citation" if intLev == 1 and isRecord("INDI, FAM") then -- Move INDI/FAM record SOURce details to dummy custom Event local strType = strPrefix..(dicLabel[strWhole] or strWhole) -- V3.3 if strArg:match("^ %u+$") then strType = strArg end -- V2.2 STORY for FMP doInsert("1 EVEN",intLine) intLine = intLine + 1 -- Insert custom EVENt TYPE Whole Record Citation or STORY doInsert("2 TYPE "..strType,intLine) intLine = intLine + 1 for intNext = intLine+1, #tblRecord do -- Demote level for all SOURce and subsidiary lines local strNext = tblRecord[intNext] local intNxt, strTag, strAny, isLink = getParts(strNext) if intNxt <= 1 and strTag ~= "SOUR" then break end if strArg == "Source Note" and strTag == "SOUR" and not isLink then local intLine = tblRecord.Line -- Convert Source Note to synthetic Source Record for Heredis -- V1.9 tblRecord.Line = intNext strNext = doSourNote(tostring(intNxt),strAny) or strNext tblRecord.Line = intLine end doModify((strNext:gsub("^%d+",tostring(intNxt+1))),intNext) end strLev = "2" intLev = 2 end local strRid = strVal:match("^@(S%d+)@$") -- Link to Source Record (not Source Note) if strArg == "Copy Media" and strRid then for intObje, strObje in ipairs (dicSour[strRid] or { }) do -- Copy the Media & _LINK structure from Source to Citation for FTL/FTM local intTag = getParts(strObje) strObje = strObje:gsub("^%d",tostring(intTag + intLev)) -- Adjust tag level number intLine = intLine + 1 doInsert(strObje,intLine) -- Insert Media tag line into Citation end for intNext = intLine+1, #tblRecord do -- Move Source Citation Note to Text From Source in name tag format -- V1.9 local strNext = tblRecord[intNext] local intNxt, strTag, strAny = getParts(strNext) if intNxt <= intLev then break end if intNxt-1 == intLev and strTag == "NOTE" then -- Found Source Citation NOTE tag local intTxt = intNxt+1 local arrNote = {} local intNote = intNext local strNote = intNxt.." NOTE " local dicRule = dicRule[" @O@"] or {Arg={}} -- V3.3 tblRecord.Arg = "Citation Note" -- V3.3 while intNote <= #tblRecord do -- Convert NOTE/subsidiary lines to name tag format -- V1.9 local intLev, strTag, strVal = getParts(tblRecord[intNote]) if intLev < intNxt then break end doMakeNote(arrNote,strNote,intLev,strTag,strVal) -- Make each line into name tag V1.9 if dicRule.Arg and dicRule.Arg[2] == "M" then -- Remove name tag for FTL- arrNote[#arrNote] = arrNote[#arrNote]:gsub("^(%d %u%u%u%u ).-\t\t","%1") -- V3.3 end strNote = intTxt.." CONT " doDelete(intNote) end doLocalNote(arrNote,intNote) -- Insert name tag NOTE lines V1.9 strAny = arrNote[1]:gsub("^%d NOTE ","") -- Move to Text From Source DATA/TEXT tag addNoteText(strAny,intNxt.." DATA",intTxt.." TEXT",intNext) break end end elseif strArg == "Source Note" and not strRid then -- Convert Source Note to synthetic Source Record for Heredis return doSourNote(strLev,strVal) end return strLev.." SOUR "..strVal -- Original text line or demoted SOUR line end -- local function doCitation local function getSynthSour(strPid,isTags) -- Convert Id to synthetic Source Record tags for Place Record -- strPid ~ Place record id -- isTags ~ Tags required? local strSid = dicPlac[strPid] or "S" -- Source Record Id (should never need to use "S") local strLabel = dicLabel["Place Record"] or "Place Record" -- V3.3 local strTitle = strPrefix..strLabel..strRecordName(strPid) -- V3.3 dicName[strSid] = strTitle -- Use Place Rec: Place Name as Source Title local strTag = tblRecord.Arg:match("(1 %u%u%u%u )") or "1 TITL " doInsert(strTag..strTitle, 2) -- Use Place Name as Source Title/Short Title -- V2.0 GSP FH5 if isTags then doInsert("1 _TYPE Place Details", 3) -- Type may get moved to Local Note doInsert("1 AUTH Place Rec Id ["..strPid.."]", 4) end return "0 @"..strSid.."@ SOUR" -- Replace Place header with Source header end -- local function getSynthSour local function doAnyRecord(strInit,strRid) -- Save all Record & Fact tags in local Note -- V1.8 FTL -- strInit ~ Initial letter of record id -- strRid ~ Numerical part of record id if dicMaxId[strInit] and dicMaxId[strInit] < tonumber(strRid) then return nil end -- Synthetic records do not need processing local intLine = tblRecord.Line + 1 local function doHandleTag(intTop,dicTag,arrNote,strNote,intKeep) local intLev, strTag, strVal, isLink = getParts(tblRecord[intLine]) if not ( intLev == intTop and dicTag[strInit] and dicTag.X ) then doMakeNote(arrNote,strNote,intLev,strTag,strVal) -- Add tag line to local Note unless eXcluded end if intKeep == intLev then -- Keep tag lines intLine = intLine + 1 if strTag == "OBJE" or strTag == "SOUR" then -- Including OBJE/SOUR subsidiary lines intLev, strTag, strVal, isObje = getParts(tblRecord[intLine]) while intLev > intTop do if strTag == "OBJE" or strTag == "SOUR" or not isLink then if strTag == "OBJE" then isLink = isObje end -- If local OBJE/SOUR, add tag line to local Note doMakeNote(arrNote,strNote,intLev,strTag,strVal) end intLine = intLine + 1 intLev, strTag, strVal, isObje = getParts(tblRecord[intLine]) end end else doDelete(intLine) -- Delete tag line end end -- local function doHandleTag local arrNote = {} local strNote = "1 NOTE " if strInit == "N" then strNote = "1 CONT " end -- Note record needs 1 CONT doMakeNote(arrNote,strNote,0,"RECID",strRecordText(strInit:upper()..strRid)) -- V2.2 if strInit ~= "N" then strNote = "2 CONT " end -- All others need 2 CONT while intLine <= #tblRecord do local strLine = tblRecord[intLine] local intLev, strTag, strVal = getParts(strLine) if intLev == 0 or strLine == "1 chan" then break end -- V2.2 local dicTag = doNameTag(strTag) if dicTag[strInit] == 0 then -- Level 1 INDI/FAM Fact local arrNote = {} doMakeNote(arrNote,"2 NOTE ",intLev,strTag,strVal) intLine = intLine + 1 while intLine <= #tblRecord do local intLev, strTag = getParts(tblRecord[intLine]) if intLev <= 1 then intLine = doLocalNote(arrNote,intLine) -- Insert local Note at end of Fact break end local dicTag = doNameTag(strTag) doHandleTag(2,dicTag,arrNote,"3 CONT ",dicTag.K) -- Keep/Remove Fact tag and put in Fact local Note? end else doHandleTag(1,dicTag,arrNote,strNote,dicTag[strInit]) -- Keep/Remove Record tag and put in Record local Note? end end if strInit ~= "N" or #arrNote > 1 then -- Omit if Note record and no subsidiary tags -- V2.6 doLocalNote(arrNote,intLine) -- Insert local Note at end of record end if strInit == "P" then return getSynthSour(strInit..strRid) -- Convert Place header to Source header end if strInit == "O" then return "0 @M"..strRid.."@ OBJE" -- Convert Object to Media initial letter end if strInit == "N" and dicNote["N"..strRid] then -- Special fix for Legacy Note record tabs (Research) and (Medical) local intLine = tblRecord.Line local strConc = tblRecord[intLine]:gsub("^0 @N%d+@ NOTE","1 CONC") strConc = strConc:gsub("^1 CONC (%(%u%l-%):) *","1 CONC %1") -- 1 CONC (Research): or (Medical): remove spaces -- V4.0 doInsert(strConc,intLine+1) return "0 @"..dicNote["N"..strRid].."@ NOTE " -- NOTE record tag with CONC text on next line end return nil -- Retain original line end -- local function doAnyRecord local function doFactCause(strVal) -- Remove Cause except for Death Event -- V1.8 FTL/FTM -- V2.0 LFT -- strVal ~ Value of CAUS line for intPrev = tblRecord.Line-1, 1, -1 do -- Search previous lines in Record buffer for a Fact local intLev, strTag = getParts(tblRecord[intPrev]) if intLev == 1 then if strTag == "DEAT" then return nil end -- Retain DEATh CAUSe line break end end if tblRecord.Arg then doFactNote(strVal) end -- If label move to Fact Note -- V3.8 return "" -- Delete original line end -- local function doFactCause local function doAddr2Even(strAddr) -- Move Address to Event Values for FTL/FTM -- V1.8 -- V3.3 -- strAddr ~ Address of ADDR line for intPrev = tblRecord.Line-1, 1, -1 do -- Search previous lines in Record buffer strAddr = strTidyText(strAddr) -- V2.2 local strPrev = tblRecord[intPrev] local intLev, strTag, strVal = getParts(strPrev) -- Search previous tag details for level 1 Fact if intLev == 1 then if #strVal == 0 and doNameTag(strTag).E then -- Found previous Event (not Attribute) -- V2.3 cater for UDF tag doModify(strPrev.." "..strAddr,intPrev) -- Append tidy Address line to Event value else doFactNote(strAddr) -- V4.0 end break end end return doRemoveAll("2") -- Remove original Address line and subsidiary tags -- V3.3 end -- local function doAddr2Even local function strTidyAddr(strAddr) -- Tidy the ADDRess line and subsidiary CONT lines -- V3.2 -- strAddr ~ Address of ADDR line --[=[ for intNext = tblRecord.Line+1, #tblRecord do local intLev, strTag, strVal = getParts(tblRecord[intNext]) if strTag == "CONT" then local strNew = strTidyText("~"..strVal):gsub("^~","") -- Prevent CONT leading comma being tidied -- V3.8 -- V4.0 if strNew ~= strVal then doModify(tostring(intLev).." CONT "..strNew,intNext) -- V4.0 end else break end end --]=] return strTidyText(strAddr) end -- local function strTidyAddr local function doAddrTidy(strLev,strAddr) -- Tidy Address field -- V2.3 -- strLev ~ Level of ADDR line -- strAddr ~ Address of ADDR line return strLev.." ADDR "..strTidyAddr(strAddr) end -- local function doAddrTidy local function doAddr2Plac(strAddr) -- Join all Address fields and move to Place field -- V3.2 -- V3.3 -- strAddr ~ Address of ADDR line local arrAddr = { strAddr; } for intNext = tblRecord.Line+1, #tblRecord do -- Add each Address sub-field value to array of fields local intNxt, strTag, strVal = getParts(tblRecord[intNext]) if intNxt <= 2 then break end table.insert(arrAddr,strVal) end doRemoveAll("2") -- Erase those Address sub-fields -- V3.3 strAddr = strTidyText(table.concat(arrAddr,", ")) return doPutOther("2","at "..strAddr) -- Move all those Address values to Place field -- V3.3 end -- local function doAddr2Plac local function doHeaderRec() -- Optionally copy Header to Source, then insert File Root INDI Record -- V1.8 FTL/FTM -- V2.0 if tblRecord.Arg then -- V3.8 intSour = intSour + 1 -- Add warning synthetic Source Record doInsert("0 @S"..intSour.."@ SOUR") doInsert("1 TITL "..strPrefix.."~ DO NOT ALTER ANY SYNTHETIC "..strPrefix.."RECORDS BELOW ~") doInsert("1 NOTE Max NOTE Id:\t"..dicMaxId.N) -- Save max original Record Id doInsert("2 CONT Max SOUR Id:\t"..dicMaxId.S) doInsert("2 CONT Max OBJE Id:\t"..dicMaxId.O) intSour = intSour + 1 -- Create synthetic Header Source Record doInsert("0 @S"..intSour.."@ SOUR") doInsert("1 TITL "..strPrefix..tblRecord.Arg) -- Create synthetic Header Source Title local intSave = #tblRecord -- Save synthetic line number for doAnyRecord("H","0") below for intLine = 2, #tblRecord do local strLine = tblRecord[intLine] if strLine:match("^0 @S") then break end -- Copy HEAD lines to synthetic Source Record doInsert(strLine) end tblRecord.Line = intSave -- Convert synthetic Header record to name tags doAnyRecord("H","0") tblRecord.Line = 1 end if dicName.RootRec then for intLine, strText in ipairs ( dicName.RootRec ) do -- Insert File Root Individual Record after HEAD record -- V1.8 doInsert(strText) tblLineNo[#tblLineNo] = dicName.RootLin[intLine] end end return nil -- Retain original line end -- local function doHeaderRec local function doHeadSour() -- Change Gedcom Product Source to Target Program Title -- V1.8 local strArg = tblRecord.Arg[strAbbr] -- V3.3 if strArg then doRemoveAll("1") end return "1 SOUR "..( strArg or "FAMILY-HISTORIAN" ) -- If no target name, use modified FH name to improve import to FH testing -- V4.0 use valid "-" instead of invalid space end -- local function doHeadSour local function doHeadVers() -- Change Gedcom Product Version to Target Product Version -- V4.0 local intLine = tblRecord.Line + 1 if tblRecord[intLine]:match("2 VERS ") then doModify("2 VERS "..tblRecord.Arg,intLine) end return nil -- Retain original line end -- local function doHeadVers local function doHeadName() -- Change header Name to Plugin Version -- V3.3 return "2 NAME Export Gedcom File Plugin Version"..(iup_gui.Version:gsub(" $","")) -- Prune trailng space -- V4.0 end -- local function doHeadName local function doVersion(strVer) -- Change export Gedcom Version to 5.5 or 5.5.1 -- V4.0 -- strVer ~ Version in VERS line if tblRecord[tblRecord.Line-1] == "1 GEDC" then local strArg = tblRecord.Arg strGedImport = strVer or "5.5.1" -- Set GEDCOM import & export version strGedExport = strArg or strVer strFact = tblGedSelect[strGedExport].Fact -- Custom fact tag for GEDCOM 5.5 = 1 EVEN and GEDCOM 5.5.1 = 1 FACT -- V4.0 strMsid = tblGedSelect[strGedImport].Msid -- Media sequence id tag for FH V6 = _ASID and for FH V7 = _SEQ -- V4.0 return "2 VERS "..strGedExport -- Output Gedcom version end return nil -- Retain original line end -- local function doVersion local function doSrcTempLnk(strRid) -- Convert a Source Template link and Templated Source Metafields -- V4.0 -- strRid ~ Record id of Source Template local strRef = tblRecord.Arg local strArg = strRef if strArg then -- Convert to a Note Record link local intLine = tblRecord.Line intLine = intLine + 1 while intLine < #tblRecord do local intLev, strTag, strVal = getParts(tblRecord[intLine]) if strTag == "_FIELD" then -- Convert template Field name to Title case strArg = strVal:match("^%u%u%-(.+)$"):lower():gsub("_"," ") strArg = strArg:gsub("(%l)(%l+)",function(strHead,strTail) return strHead:upper()..strTail end ) doDelete(intLine) elseif intLev == 2 then -- Convert template Field value line if strTag == "REPO" then doInsert("1 REPO "..strVal,intLine) -- Use generic Repository link intLine = intLine + 1 strVal = strRecordName(strVal:match("@(R%d+)@")) -- Translate link to Repository name and add to Note -- V4.5 elseif strTag:match("^_") then break end if strTag == "NAME" then -- Adjust surname to upper case strVal = strVal:gsub("/(.-)/",function(strName) return strName:upper() end ) end tblRecord.Arg = strArg..":\t" doRecNote(strVal,intLine) -- Convert template lines to 1 CONT Note Record format elseif strRef == "Reformat" then if intLev == 1 then if strTag == "TITL" then -- Reformat TITL and ABBR fields -- V4.2 local strRid = tblRecord[1]:match("0 @S(%d+)@ SOUR") local ptrSour = fhNewItemPtr() ptrSour:MoveToRecordById("SOUR",tonumber(strRid)) -- Pointer to actual Project Source record local ptrTitl = fhGetItemPtr(ptrSour,"~._SRCT>TITL") local strTitl = fhGetValueAsText(ptrTitl) -- Title Template local ptrFoot = fhGetItemPtr(ptrSour,"~._SRCT>FOOT") local strFoot = fhGetValueAsText(ptrFoot) -- Footnote Template fhSetValueAsText(ptrTitl,strFoot) fhSrcEnableAutoTitle(ptrSour,true) -- Obtain the Footnote text local strFoot = fhGetValueAsText(fhGetItemPtr(ptrSour,"~.TITL")) fhSetValueAsText(ptrTitl,strTitl) fhSrcEnableAutoTitle(ptrSour,true) -- Restore the Title text doModify("1 TITL "..strFoot,intLine) -- Modify the 1 TITL strFoot intLine = intLine + 1 local intLev, strTag, strOld = getParts(tblRecord[intLine]) if intLev == 1 and strTag == "ABBR" then doModify("1 ABBR "..strVal.." "..strOld,intLine)-- Modify old 1 ABBR strVal + strOld else doInsert("1 ABBR "..strVal,intLine) -- Insert new 1 ABBR strVal end break elseif strTag == "NOTE" or strTag == "CHAN" then -- Escape before end of record break end end intLine = intLine + 1 else break end end strRid = strRid:match("@(T%d+)@") or "" local strNote = strRecordName(strRid) -- Translate Source Template Id to Note Record Id if it exists if not strNote:match("^@N%d+@$") then intNote = intNote + 1 -- Obtain next Note Record Id strNote = "@N"..tostring(intNote).."@" dicName[strRid] = strNote end return "1 NOTE "..strNote -- Convert link to the Note record end local intLine = tblRecord.Line + 1 while intLine < #tblRecord do -- Remove Metafields completely local strLev, strTag = tblRecord[intLine]:match("^(%d) (_FIELD) ") if strTag then doDelete(intLine) doRemoveAll(strLev) else break end end return "" -- Remove link completely end -- local function doSrcTempLnk local function doSrcTempCit(strLev) -- Convert Templated Source Citation _FIELD Metafields -- V4.2 -- strLev ~ Level of _FIELD line local dicRule = dicRule["1 _SRCT"] or {Item="Keep"} local strRule = dicRule.Item if strRule:match("^Remove") then return doRemoveAll(strLev) -- Remove Metafield completely end if strRule:match("^Move to Note") or strRule:match("^Format") then -- Convert Metafield into Citation local Note local intLine = tblRecord.Line local intOrig = intLine local intNext, intData while intLine < #tblRecord do local intLev, strTag, strVal = getParts(tblRecord[intLine]) if strTag == "_FIELD" then -- Convert template Field name to Title case local strArg = strVal:match("^%u%u%-(.+)$"):lower():gsub("_"," ") local strArg = strArg:gsub("(%l)(%l+)",function(strHead,strTail) return strHead:upper()..strTail end ) tblRecord.Arg = strArg..":\t" if intLine == intOrig then intLine = intLine + 1 else doDelete(intLine) end intNext = intLine intData = intLev + 1 elseif intLine == intNext and intLev == intData then -- Convert template Field value line if strTag == "NAME" then -- Adjust surname to upper case strVal = strVal:gsub("/(.-)/",function(strName) return strName:upper() end ) end addNoteText(strVal,strLev.." NOTE") -- Convert template Field to local Note doDelete(intLine) else break end end return "" -- Remove original _FIELD line end return nil -- Retain original _FIELD line end -- local function doSrcTempCit local function doSrcTempRec(strRid) -- Convert a Source Template record -- V4.0 -- strRid ~ Record id of Source Template local dicRule = dicRule["1 _SRCT"] or {Item="Keep"} local strRule = dicRule.Item if strRule:match("^Remove") then return doRemoveAll("0") -- Remove record completely end if strRule:match("^Move to Note") or strRule:match("^Format") then strRecT = "NOTE" -- To get doRecNote() to use Note record format local intLine = tblRecord.Line + 1 while intLine < #tblRecord do local strLine = tblRecord[intLine] if strLine:match("^1 CONT") then break else doRecNote(strLine,intLine) -- Convert template lines to 1 CONT Note Record format end end local strNote = strRecordName(strRid) -- Translate Source Template Id to Note Record Id if not strNote:match("^@N%d+@$") then intNote = intNote + 1 -- Obtain next Note Record Id strNote = "@N"..tostring(intNote).."@" dicName[strRid] = strNote end return "0 "..strNote.." NOTE "..dicLabel[tblRecord.Arg] -- Convert record header to Note record end return nil -- Otherwise retain original record header end -- local function doSrcTempRec local function doResNoteLnk(strLev,strRid) -- Convert a Research Note link -- V4.0 -- strLev ~ Level of _RNOT line -- strRid ~ Record link id if tblRecord.Arg then -- Convert to a Note Record link strRid = strRid:match("@(E%d+)@") or "" local strNote = strRecordName(strRid) -- Translate Research Note Id to Note Record Id if it exists local dicArg = dicRule[" @N@"] or {} local dicArg = dicArg.Arg or {} if dicArg.Research == "HI" then if not strNote:match("^@HI%d+@$") then dicNote.HI = dicNote.HI + 1 -- Obtain next Legacy Research Note Record Id strNote = "@HI"..tostring(dicNote.HI).."@" dicName[strRid] = strNote end else if not strNote:match("^@N%d+@$") then intNote = intNote + 1 -- Obtain next Note Record Id strNote = "@N"..tostring(intNote).."@" dicName[strRid] = strNote end end return strLev.." NOTE "..strNote -- Convert link to the Note record end return "" -- Remove link completely end -- local function doResNoteLnk local function doResNoteRec(strRid) -- Convert a Research Note record -- V4.0 -- strRid ~ Record id local getRule = dicRule[" _RNOT"] or {Item="Keep"} local strRule = getRule.Item if strRule:match("^Remove") then return doRemoveAll("0") -- Remove record completely end if strRule:match("^Move to Note") then local strNote = strRecordName(strRid) -- Translate Research Note Id to Note Record Id local dicArg = dicRule[" @N@"] or {} local dicArg = dicArg.Arg or {} if dicArg.Research == "HI" then -- Process as Legacy Research Note if not strNote:match("^@HI%d+@$") then dicNote.HI = dicNote.HI + 1 -- Obtain next Legacy Research Note Record Id strNote = "@HI"..tostring(dicNote.HI).."@" dicName[strRid] = strNote end for intLine = 2, #tblRecord do -- Convert 1 TEXT & 2 CONT to Legacy Research Note Record format local strLine = tblRecord[intLine] strLine = strLine:gsub("^1 TEXT ?","1 CONC (Research):") strLine = strLine:gsub("^2 CONT ?","1 CONT ") doModify(strLine,intLine) end return "0 "..strNote.." NOTE " -- Convert record header to Legacy Research Note record else if not strNote:match("^@N%d+@$") then -- Process as standard Note Record intNote = intNote + 1 strNote = "@N"..tostring(intNote).."@" -- Obtain next Note Record Id dicName[strRid] = strNote end for intLine = 2, #tblRecord do -- Convert 1 TEXT & 2 CONT to 1 CONT Note Record format local strLine = tblRecord[intLine] strLine = strLine:gsub("^1 TEXT ?","1 CONT ") strLine = strLine:gsub("^2 CONT ?","1 CONT ") doModify(strLine,intLine) end return "0 "..strNote.." NOTE "..dicLabel[tblRecord.Arg] -- Convert record header to Note record with Arg first line end end return nil -- Otherwise retain original record header end -- local function doResNoteRec local function doSubm2Sour(strInit,strRid) -- Convert Submitter/Submission Record to Source Record -- V1.8 FTL/FTM -- V2.0 GSP -- strInit ~ Initial letter of record id -- strRid ~ Numerical part of record id local strLab = dicLabel[tblRecord.Arg] or tblRecord.Arg -- V3.3 local intLev, strTag, strName = getParts(tblRecord[2]) if strTag ~= "NAME" then strName = "" end strName = strLab..strName.." ["..strInit..strRid.."]" -- V3.3 doInsert("1 TITL "..strPrefix..strName, 2) -- Use Submitter Name as synthetic Source Title tblRecord.Line = 2 -- Convert synthetic record to name tags doAnyRecord(strInit,strRid) tblRecord.Line = 1 intSour = intSour + 1 return "0 @S"..intSour.."@ SOUR" -- Replace Submitter/Submission header with Source header end -- local function doSubm2Sour local function doRepoRec(strInit,strRid) -- Put some Repository tags into Source for FTL/FTM only -- V1.8 -- strInit ~ Initial letter of record id -- strRid ~ Numerical part of record id local intLine = tblRecord.Line local intLev, strTag, strName repeat intLine = intLine + 1 intLev, strTag, strName = getParts(tblRecord[intLine]) -- Cope with links to Named List synthetic Notes until strTag ~= "NOTE" if (intLev..strTag) ~= "1NAME" then -- Ensure Repository record has a Name doInsert("1 NAME ",intLine) strName = "" end strName = strName.." [R"..strRid.."]" -- Add the Record Id to Repository Name doModify("1 NAME "..strName,intLine) intSour = intSour + 1 -- Use next available Source Record Id doInsert("0 @S"..intSour.."@ SOUR") doInsert("1 TITL "..strPrefix.."Repository: "..strName) -- New synthetic Source Record header & title -- V3.3 local intSave = #tblRecord -- Save synthetic line number for doAnyRecord() below local intLine = 2 while intLine < #tblRecord do -- Move some Repository tag lines to synthetic Source local strLine = tblRecord[intLine] if strLine:match("^0 @S") then break end -- Escape at end of record local intLev, strTag, strVal = getParts(strLine) if doNameTag(strTag).R then -- V1.9 -- V2.3 cater for UDF tag intLine = intLine + 1 -- Retain NAME, ADDR, PHON, _EMAIL, _WEB lines if strTag == "ADDR" then doInsert(strLine) end -- Copy ADDR to synthetic Source -- V1.9 else doInsert(strLine) -- Move line to synthetic Source -- V1.9 doDelete(intLine) intSave = intSave - 1 end end tblRecord.Line = intSave -- Convert synthetic record to name tags doAnyRecord("r",strRid) -- V1.9 tblRecord.Line = 1 return nil -- Otherwise retain original record header end -- local function doRepoRec local function doRepoNote(strInit,strRid) -- Put most Repository tags into Notes for Ancestry -- V2.2 -- strInit ~ Initial letter of record id -- strRid ~ Numerical part of record id local intLine = tblRecord.Line + 1 while intLine < #tblRecord do -- Move some Repository tag lines to Note field local strLine = tblRecord[intLine] local intLev, strTag, strVal = getParts(strLine) if doNameTag(strTag).T then -- V2.3 cater for UDF tag -- V4.0 A changed to T intLine = intLine + 1 -- Keep tag line for Ancestry Family Tree else doDelete(intLine) local strName = doNameTag(strTag).Name:gsub("^#",intLev) -- V2.3 cater for UDF tag doRecNote(strName..getRidName(strVal)) -- Move Name Tag line to Note end end return nil -- Retain original record header end -- local function doRepoNote local function doRepoLink(strRid) -- Convert 2nd & subsequent REPO link to record local Note -- V4.0 -- strRid ~ Record link id local intLine = tblRecord.Line while intLine > 2 do -- Search previous lines intLine = intLine - 1 local strLine = tblRecord[intLine] if strLine:match("^1 REPO ") then -- Predecessor REPO link instance found local strName = getRidName(strRid) -- Lookup Id & Name of Repository record intLine = tblRecord.Line + 1 while intLine < #tblRecord do -- Search following lines local strLine = tblRecord[intLine] local intLev, strTag, strVal = getParts(strLine) -- Convert any 2 CALN and 3 MEDI tags if intLev > 1 then strName = strName..doNameTag(strTag).Name:gsub("^#%)","\t")..strVal doDelete(intLine) -- Delete the 2 CALN and 3 MEDI tags else break end end return doRecNote(strName) -- Move Repository details to Note end end return nil -- Retain original line 1st REPO link end -- local function doRepoLink local function doNoteLine(strNote,strText) -- Move leading Note Record text to next line for FindMyPast -- V2.2 Heredis -- V2.7 -- strNote ~ Note record preamble -- strText ~ Note record text -- doInsert("1 CONT "..strText,tblRecord.Line+1) -- V2.2 or may need tblRecord.Arg = "CONT" or "CONC" doInsert("1 CONC "..strText,tblRecord.Line+1) -- V2.7 return strNote -- "NOTE " removed -- V3.3 end -- local function doNoteLine local dicPlac2Sour = { ["1 STAN"]="1 ABBR"; ["1 STAT"]="1 PUBL"; ["1 MAP"]="1 TEXT Map Plot:"; ["2 LATI"]="2 CONT Latitude: "; ["2 LONG"]="2 CONT Longitude:"; } local function doPlac2Sour(strInit,strRid) -- Convert Place Record to Source Record -- V1.6 -- strInit ~ Initial letter of record id -- strRid ~ Numerical part of record id for intLine = 2, #tblRecord do -- Search through record local strLine = tblRecord[intLine] strLine = strLine:gsub("^(%d %u+)",dicPlac2Sour) if tblRecord.Arg:match("ABBR") then strLine = strLine:gsub("^1 ABBR","1 TITL") end -- V2.0 doModify(strLine,intLine) end return getSynthSour(strInit..strRid,"_TYPE & AUTH") -- Convert Place header to Source header end -- local function doPlac2Sour local function doPlaceTidy(strRid,strPlac) -- TNG/GST Place name tidy -- V3.3 -- strRid ~ Whole record id -- strPlac ~ Place tag & name local strPlac = strPlac:match("^_PLAC (.*)$") strPlac = strTidyText(strPlac) if tblRecord.Arg:match("NoRecId") then strRid = "" end -- Remove Rec Id for TNG return "0 "..strRid.."_PLAC "..strPlac end -- local function doPlaceTidy local function doRidValue(strTag,strVal) -- Convert INDI/FAM Standard Tag Value to Rid and Name -- V2.2 -- strTag ~ Tag for current line -- strVal ~ Value of current line if isRecord(tblRecord.Arg) then return "1 "..strTag..getRidName(strVal) end return nil end -- local function doRidValue local function doCustEvent(strTag,strVal) -- Convert INDI/FAM Standard Tag to synthetic Custom Event -- V1.8 FTL/FTM -- V1.9 HER -- strTag ~ Tag for current line -- strVal ~ Value of current line if isRecord(tblRecord.Arg) then local intLine = tblRecord.Line local intL, strT, strV = getParts(tblRecord[intLine+1]) -- V4.0 -- Check for 2 TYPE description if intL == 2 and strT == "TYPE" then strVal = strVal.." ~ "..strV doDelete(intLine+1) end local dicNameTag = doNameTag(strTag) if dicNameTag.E or dicNameTag.L -- V3.9 or ( isRecord("FAM") and strFact == "1 FACT" ) then -- V4.0 doInsert("1 EVEN"..getRidName(strVal),intLine) -- V3.9 -- Use EVEN for Events & LDS & FAMily FACT even for 5.5.1 else doInsert(strFact..getRidName(strVal),intLine) -- V1.9 -- V2.8 end local strType = tblRecord.Arg:match(" %u+$") or "" -- V2.2 FMP "CENSUSFAMILY" if #strType <= 4 then local strName = dicNameTag.Name:match("^#. (.+:)") or "" -- V2.3 cater for UDF tag -- V3.3 -- V3.9 strType = strPrefix..strName..strTag -- Add prefix to differentiate a synthetic Custom Event from Custom _ATTR and be consistent with other synthetic items end return "2 TYPE "..strType -- Change original line end return nil -- Retain original line end -- local function doCustEvent local function doCust_Attr(strVal) -- Convert Custom Attribute to Custom Event/Fact -- strVal ~ Value of _ATTR/FACT line if isRecord("FAM") and strFact == "1 FACT" then -- V4.0 tblRecord.Arg = "Attribute Value" -- FAMily records do not allow FACT in 5.5.1 -- V3.8 end if tblRecord.Arg then -- V3.3 -- V3.8 doFactNote(strVal) -- If label, move value to local Note return "1 EVEN" -- Convert _ATTR/FACT to EVEN and clear value -- V2.8 end return strFact.." "..strVal -- Convert _ATTR/FACT to EVEN/FACT and keep value -- V2.8 end -- local function doCust_Attr local function doFact_Rule(strVal) -- Convert Family custom Attribute according to 1 FACT rule -- V4.0 local getRule = dicRule["1 FACT"] or {} local strOld = getRule.Old or "" tblRecord.Arg = getRule.Arg if strOld == "^1 FACT (.*)" then return getRule.New(strVal) end -- Rule B "Custom Event & Value" & Rule C "Custom Event & Note" if strOld == "^(1 FACT) (.*)" then return getRule.New("1 _ATTR",strVal) end -- Rule F "Keep Tag & Sub Notes" if strOld == "^1 FACT" then return nil end -- Rule D "Change to _ATTR" if strOld == "^(1) FACT .*" then return doRemoveAll("1") end -- Rule E "Remove entirely" return doCust_Attr(strVal) -- Rule A "Keep Standard Tag" end -- local function doFact_Rule local function doAttrNotes(strPref,strVal) -- Convert Attribute subsidiary fields to local Note -- V4.0 -- strPref ~ Attribute level & tag -- strVal ~ Attribute value if strPref == "1 _ATTR" then strPref = strFact end -- Convert _ATTR/FACT to EVEN/FACT if strPref == "1 FACT" and isRecord("FAM") then tblRecord.Arg = "Attribute Value" -- FAMily records do not allow FACT in 5.5.1 doFactNote(strVal) -- Move value to local Note return "1 EVEN" -- Convert _ATTR/FACT to EVEN and clear value end local intLine = tblRecord.Line + 1 while intLine < #tblRecord do -- Check subsidiary fields local strLine = tblRecord[intLine] local intLev, strTag, strVal = getParts(strLine) if intLev <= 1 or strTag == "SOUR" or strTag == "NOTE" then break end if strTag == "TYPE" then -- Keep some subsidiary fields intLine = intLine + 1 else local strName = doNameTag(strTag).Name:gsub("^#",intLev) -- Name according to dictionary above -- V2.3 cater for UDF tag tblRecord.Arg = strName doFactNote(strVal) -- Move some subsidiary fields to a local Note doDelete(intLine) end end return strPref.." "..strVal -- Retain attribute and keep value end -- local function doAttrNotes local function doConvert(strTag) -- Convert one event to another if that does not exist for FTD -- V3.3 -- strTag ~ Tag for current line local strArg = tblRecord.Arg -- Other event tag local intLine = tblRecord.Line for intPrev = intLine-1, 1, -1 do -- Search previous event tags local intLev, strTag = getParts(tblRecord[intPrev]) if intLev == 1 and strTag == strArg then return nil end -- Retain original line as other event already exists end for intNext = intLine+1, #tblRecord do local strNext = tblRecord[intNext] local intLev, strTag = getParts(strNext) if intLev < 2 then break end if intLev == 2 and strTag == "DATE" then -- Find the Date for the Event strNext = strNext:gsub(" %d?%d? ?[ADFJMNOS][ACEOPU]%u "," "):gsub("DATE (%d%d%d%d)$","DATE ABT %1") doModify(strNext,intNext) -- Reduce the date to just the year(s) break end end return ( tblRecord[intLine]:gsub(strTag,strArg) ) -- Convert event tag to other event end -- local function doConvert local function doPlaceTag(strLev,strName) -- Add citation of Place synthetic Source Record to Place field -- V1.6 -- strLev ~ Level of PLAC line -- strName ~ Name of PLAC line local strRid = dicCite[strName] -- V1.8 if strRid then -- Except when no synthetic Source Record if not isRecord("SOUR") then -- or Place inside SOURce record DATA local intLev = tonumber(strLev) -- Default to use Place level in case Place Source link not allowed local intLine = tblRecord.Line if tblRecord.Arg:match("Place") then -- V2.0 Heredis & Legacy -- V2.2 ANCestry do not allow Place Source link for intPrev = intLine-1, 1, -1 do -- Search previous lines in Record buffer local intPrv, strTag = getParts(tblRecord[intPrev]) if intPrv < intLev then -- Unless LDS Ordination then use level below Place level for Source link if not doNameTag(strTag).L then -- V1.9 -- V2.3 cater for UDF tag -- V4.0 .L intLev = intLev + 1 intLine = intLine + 1 -- V1.9 end break end end end doInsert(tostring(intLev).." SOUR @"..strRid.."@",intLine) -- Add citation link to Source Rec Id end else local dicMap = dicPlac[strName] -- Reproduce MAP, LATI, LONG structure for GEDCOM 5.5.1 Place fields -- V3.8 if dicMap then local intLine = tblRecord.Line local intLev = tonumber(strLev) for intPrev = intLine-1, 1, -1 do -- Search previous lines in Record buffer -- V4.0 local intPrv, strTag = getParts(tblRecord[intPrev]) if intPrv < intLev then -- Unless LDS Ordination then add MAP structure -- V4.0 if not doNameTag(strTag).L then -- V1.9 -- V2.3 cater for UDF tag -- V4.0 .L for _, strMap in ipairs (dicMap) do local intMap, strTag, strVal = getParts(strMap) local strLine = tostring(intLev + intMap).." "..strTag.." "..strVal intLine = intLine + 1 doInsert(strLine,intLine) end end break end end end end if tblRecord.Arg:match("Tidy") then strName = strTidyText(strName) -- Remove redundant commas -- V2.2 end return strLev.." PLAC "..strName end -- local function doPlaceTag local dicCiteNote = {FILE=" Object";FORM=" Object";TITL=" Object";OBJE=" Object";FAMC=" Parent";FAMS=" Spouse";} local function doCiteNote(strLev,strVal) -- Convert Individual/Family/Fact local Note/Note Record to Citation Text From Source for Ancestry -- V2.2 -- strLev ~ Level of current line -- strVal ~ Value of current line local intLev = tonumber(strLev) if isRecord("INDI, FAM") and intLev <= 2 then local isLink = strVal:match("^@N%d+@$") local putNote = true local strNote = strPrefix.."Note" -- V3.3 local intLine = tblRecord.Line if not intCite then -- Create synthetic "Note" Source record intSour = intSour + 1 intCite = intSour table.insert(tblRecord,"0 @S"..intCite.."@ SOUR") table.insert(tblRecord,"1 TITL "..strNote) end local intPre, strTag = getParts(tblRecord[intLine-1]) -- Check previous line tag -- V2.3 local strType = strTag:gsub("^.+$",dicCiteNote) -- Need synthetic custom Event if Source Citation not allowed if intLev == 1 then strType = " Record" end if strType ~= strTag then local strName = doNameTag(strRecT).Name:match("^#. (.+):") or "" -- V2.3 cater for UDF tag -- V3.3 if strType == " Object" then for intPrev = intLine-1, intLine-5, -1 do -- Obtain title of object -- V2.3 local intPre, strTag, strVal = getParts(tblRecord[intPrev]) if strTag == "TITL" then strName = strVal -- V3.3 break end end end doInsert("1 EVEN",intLine) -- Create synthetic "Note" custom Event for Record/Object/Parent/Spouse Note intLine = intLine + 1 doInsert("2 TYPE "..strPrefix..strName..strType.." Note",intLine) -- V2.3 intLine = intLine + 1 end doInsert("2 SOUR @S"..intCite.."@",intLine) intLine = intLine + 1 doInsert("3 PAGE "..strNote,intLine) -- Create synthetic "Note" Citation intLine = intLine + 1 doInsert("3 DATA",intLine) intLine = intLine + 1 intLev = intLev + 1 while intLine <= #tblRecord do intLine = intLine + 1 local strLine = tblRecord[intLine] local intOld, strTag = getParts(strLine) if intOld == intLev and strTag:match("CON[CT]") then -- Copy Note lines to Text From Source doModify((strLine:gsub("^%d","5")),intLine) elseif intOld == intLev and strTag == "SOUR" then -- Copy Note Source to Citation Note Source -- V3.3 doModify((strLine:gsub("^%d",(intLev-1))),intLine) elseif intOld > intLev then -- Must copy all subsidiary SOUR tags too -- V3.3 intOld = intOld - intLev + 2 doModify((strLine:gsub("^%d",intOld)),intLine) else break end end if isLink then return "3 NOTE "..strVal end return "4 TEXT "..strVal end return nil -- Retain original line end -- local function doCiteNote local function doNameNote(strLev,strVal) -- Move Note on Name to record Note -- V4.0 -- strLev ~ Level of current line -- strVal ~ Value of current line if strLev == "2" and isRecord("INDI") then for intLine = tblRecord.Line-1, 2, -1 do local intLev, strTag = getParts(tblRecord[intLine]) if intLev == 1 then if strTag == "NAME" then return doRecNote(strVal) -- Move to record Note end break end end end return nil -- Retain original line end -- local function doNameNote local function doWeblink(strLev,strVal) -- For citation local Note with Weblink URL add _LINK URL for FTM -- V4.6 -- strLev ~ Level of current line -- strVal ~ Value of current line local intLev = tonumber(strLev) local strUrl = strVal:match('Weblink: "([hf]t?tps?://.-)"') or ("~"..strVal):match('[^"]([hf]t?tps?://[A-Za-z0-9%-%._~:/%?#@!%$&\'\\%(%)%*%+,;=%%]+)') if intLev > 1 and strUrl then -- Found suitable Note with Weblink: "URL" or plain text URL -- V4.6 local intLine = tblRecord.Line for intPrev = intLine-1, 2, -1 do local intPre, strTag = getParts(tblRecord[intPrev]) if intPre == intLev-1 and strTag == "SOUR" then -- Found parent SOUR citation so add _LINK tag with URL -- V4.6 doInsert(intLev.." _LINK "..strUrl,intLine+1) break end end end return nil -- Retain original line end -- local function doWeblink local function doNote2Fact(strVal) -- Convert Family Record Note to synthetic Fact Note -- V2.2 -- V3.3 -- strVal ~ Value of NOTE line if isRecord("FAM") then -- V3.3 local intLine = tblRecord.Line doInsert("1 EVEN",intLine) -- Create synthetic "Family Story" custom Event for record Note intLine = intLine + 1 doInsert("2 TYPE "..tblRecord.Arg,intLine) -- V3.3 intLine = intLine + 1 local isFirstNote = strVal:match("^@N%d+@$") while intLine <= #tblRecord do intLine = intLine + 1 local strLine = tblRecord[intLine] local intOld, strTag, strVal, isLink = getParts(strLine) -- V3.3 if intOld > 1 or strTag == "NOTE" then if strTag == "NOTE" and not isLink then -- Another local NOTE -- V3.3 if isFirstNote then isFirstNote = false else doInsert("3 CONT ",intLine) intLine = intLine + 1 strLine = strLine:gsub("^1 NOTE","2 CONT") -- Concatenate notes into one -- V3.3 intOld = 2 end end doModify((strLine:gsub("^%d",intOld+1)),intLine) -- Increase all subsidiary tag levels else break end end return "2 NOTE "..strVal end if strMode ~= "GST" then return doNoteLink("1",strVal) end return nil -- Retain original line end -- local function doNote2Fact local function doRecFlags() -- Convert each Record Flag to a custom Event or local Record Note local intLine = tblRecord.Line local intNext = intLine+1 while intNext <= #tblRecord do -- Search for subsequent flag tags & names -- V1.8 local intLev, strTag, strName = getParts(tblRecord[intNext]) if intLev == 2 then if tblRecord.Item:match("Event") then -- Create custom Event -- V3.3 local strLab = dicLabel[tblRecord.Arg] or tblRecord.Arg doModify("1 EVEN",intNext) -- "1 EVEN Y" value "Y" is invalid Gedcom -- 2.8 intNext = intNext + 1 doInsert("2 TYPE "..strPrefix..strTag,intNext) -- Use Tag as Type and add prefix to indicate synthetic -- V3.3 intNext = intNext + 1 doInsert("2 NOTE "..strLab..strName,intNext) -- Use Name as Note, or vice versa -- V3.3 intNext = intNext + 1 else doDelete(intNext) doRecNote(strTag..":\t"..strName) -- Label given so create local Record Note intNext = tblRecord.Line + 1 end else break -- End search when no more flags end end return "" -- Remove original _FLGS line end -- local function doRecFlags local function do2ndPlace(strPlac) -- To/From Place for EMIG/IMMI Facts may need appropriate Note Label -- V4.0 simplified -- strPlac ~ Place name local isFact = false -- V4.0 local intLine = tblRecord.Line local strArg = tblRecord.Arg -- V3.3 if strArg:match("Tidy") then -- V2.3 strPlac = strTidyText(strPlac) end for intPrev = intLine-1, 1, -1 do -- Search previous lines in Record buffer local strPrev = tblRecord[intPrev] -- Determine Emigration/Immigration from previous Fact tag local intLev, strTag, strVal = getParts(strPrev) if intLev == 2 and strTag == "TYPE" then -- If synthetic Witness Role Event Type from EMIG/IMMI -- V3.3 if strVal:match(" Emigration ") then strArg = "Into Place" break end if strVal:match(" Immigration") then strArg = "From Place" break end elseif intLev == 1 then if strTag == "EMIG" then isFact=true strArg = "Into Place" break end if strTag == "IMMI" then isFact=true strArg = "From Place" break end break -- V3.3 end end if isFact and not tblRecord.Arg:match("Place") then return "2 _PLAC "..strPlac end -- Keep custom tag -- V3.3 -- V4.0 tblRecord.Arg = strArg return doFactNote(strPlac) -- Convert _PLAC to fact note end -- local function do2ndPlace local function doIndiName(strName) -- Process Individual Name -- strName ~ Individual name if isRecord("INDI") then tblRecord.Name = ( tblRecord.Name or 0 ) + 1 -- Count NAME instances -- V1.8 if tblRecord.Arg then -- Move Name tail to Record Note -- V3.8 local strHead, strSurn, strTail = strName:match("^(.*) ?/(.*)/ ?(.*)$") if #strTail > 0 then strName = strHead.." /"..strSurn.."/" -- Remove Name tail doRecNote(strTail) return "1 NAME "..strName end end end return nil -- Retain original line end -- local function doIndiName local function doPutName(strVal) -- Append Name Suffix field to Name field -- V1.8 -- strVal ~ Value of NSFX line for intPrev = tblRecord.Line-1, 1, -1 do -- Search previous lines in Record buffer local strPrev = tblRecord[intPrev] if strPrev:match("^1 NAME ") then doModify(strPrev.." "..strVal,intPrev) -- Append Name Suffix to previous Name text -- V1.8 return "" -- Delete original line end end return nil -- Should never get here, but keep line if it does end -- local function doPutName local function doPromote(strVal) -- Promote a tag to a higher level for TNG, FTL/FTM, GSP, etc -- V1.8 -- strVal ~ Value of current line local intArg, strArg, strOpt = getParts(tblRecord.Arg) -- Argument supplies new level & tag & option -- V2.2 if strArg == "NAME" then -- V1.8 FTL/FTM -- V2.0 GSP local strName = '"'..strVal..'" /' for intPrev = tblRecord.Line-1, 1, -1 do -- Find previous 1 NAME tag -- V1.8 FTL/FTM -- V2.0 GSP local intLev, strTag, strVal = getParts(tblRecord[intPrev]) if intLev == intArg and strTag == "NAME" then -- Insert "name" in NAME -- V1.8 FTL/FTM -- V2.0 GSP doModify("1 NAME "..strVal:replace("/",strName,1),intPrev) break end end else if strOpt == "or" then strVal = strVal:gsub(" *, *"," or ") -- Convert comma in ALIAs -- V2.2 FTL/FTM end local strText = tostring(intArg).." "..strArg.." "..strVal for intNext = tblRecord.Line+1, #tblRecord do -- Skip lower level tag lines local intLev = getParts(tblRecord[intNext]) if intLev <= intArg then doInsert(strText,intNext) -- Insert before line with matching level break end end end return "" -- Delete original line end -- local function doPromote local function doAssociate(strRecId) -- Convert Associated Person to Custom Event/Fact -- V2.2 variant -- strRecId ~ Individual record link id local strName = dicNameTag["ASSO"].Name:match("^#. (.+:)") or "" -- V3.3 local strVal = strRecordText(strRecId) if tblRecord.Arg then doFactNote(strVal) end -- V2.0 -- V3.8 doInsert(strFact.." "..strVal,tblRecord.Line) -- V2.8 return "2 TYPE "..strPrefix..strName.."ASSO" -- Synthetic Custom Event/Fact of type Associated Person -- V2.2 end -- local function doAssociate dicRelation = { Adopted = "ADOPTED_CHILD" ; Birth = "NATURAL_CHILD" ; De_Facto = "NATURAL_CHILD" ; Foster = "LEGITIMIZED_CHILD" ; Illegitimate= "ADULTEROUS_CHILD" ; LDS_Sealing = "RELATIONSHIP_UNKNOW" ; Step = "RECOGNIZED_CHILD" ; } local function doRelation(strText) -- Convert 2 _?PEDIgree to 1 _FIL for Heredis -- V1.9 -- strText ~ Value of _PEDI/PEDI line strText = strText:gsub("^(.+) %(.+%)$","%1"):gsub(" ","_") strText = dicRelation[strText] or "RELATIONSHIP_UNKNOW" return tblRecord.Arg..strText end -- local function doRelation local function doPedigree(strText) -- Note _PEDIgree against specific family -- V1.8 -- strText ~ Value of _PEDI/PEDI line for intLine = tblRecord.Line-1, 1, -1 do local intLev, strTag, strVal = getParts(tblRecord[intLine]) -- V1.9 if intLev == 1 and strTag == "FAMC" then doRecNote(strText.." for 1 FAMC "..getRidName(strVal)) -- Convert @Rid@ to [rid] + Record Name -- V1.9 break end end return "" -- Delete original line end -- local function doPedigree local function doSpouseAge() -- Family Fact HUSB/WIFE Age needs appropriate Note Label local intLine = tblRecord.Line+1 local strLine = tblRecord[intLine] or "" -- Convert subsequent AGE line to labelled Note -- V1.8 local intLev, strTag, strVal = getParts(strLine) if strTag == "AGE" then doFactNote(strVal,intLine) end return "" -- Remove original HUSB/WIFE line end -- local function doSpouseAge local function doSameGender(strTag,strVal) -- Adjust same sex Family HUSB/WIFE tags to asymmetric format -- V2.6 -- strTag ~ HUSB/WIFE tag for line -- strVal ~ Value of HUSB/WIFE line local intLev, strNxt = getParts(tblRecord[tblRecord.Line+1]) -- Check next line if intLev == 1 and strTag == strNxt then -- Found pair of HUSB or pair of WIFE tags return tblRecord.Arg..strVal -- Change HUSB to WIFE or WIFE to HUSB end return nil -- Retain original line end -- local function doSameGender local function doShortTitl(strVal) -- Move Short Title to Title or labelled Note -- strVal ~ Value of ABBR line for intPrev = 2, #tblRecord-1 do -- Search all lines in Record buffer to find Title if tblRecord[intPrev]:match("^1 TITL ") then if not tblRecord.Arg then return "" end -- FTL/FTM removes Short Title -- V2.0 -- V3.8 return doRecNote(strVal) -- Else use labelled Record Note end end return "1 TITL "..strVal -- Convert Short Title to Title end -- local function doShortTitl local function doCustomId(strVal) -- Move a Custom/Automated Id to labelled Record local Note -- strVal ~ Value of REFN/RIN line if tblRecord.Arg[2] == "ALL" or isRecord(tblRecord.Arg[2]) then tblRecord.Arg = tblRecord.Arg[1] -- Set the Record local Note label return doRecNote(strVal) end return nil -- Retain original line end -- local function doCustomId local dicDateWords = -- Table of DATE Tag word conversions from UPPER CASE to Title Case, only for doDateField() { JAN = " Jan" ; -- Months FEB = " Feb" ; MAR = " Mar" ; APR = " Apr" ; MAY = " May" ; JUN = " Jun" ; JUL = " Jul" ; AUG = " Aug" ; SEP = " Sep" ; OCT = " Oct" ; NOV = " Nov" ; DEC = " Dec" ; ABT = " Abt" ; -- Qualifiers CAL = " Cal" ; EST = " Est" ; FROM= " From"; -- Periods TO = " To" ; BEF = " Bef" ; -- Ranges AFT = " Aft" ; BET = " Bet" ; AND = " And" ; INT = " Int" ; -- Interpreted phrase } local function doDateField(strText) -- Reformat any date field; used by doLocDate(), doObjNote() -- strText ~ Entire DATE line local strForm, intSize = dicRule[" DATE"].Arg:match("^(%a+) ?(%d-)$") -- V2.0 intSize = tonumber(intSize) or 999 if strForm == "Title" then -- Convert DATE tag words from UPPER to Title case -- V2.0 local strPhrase = strText:match("^%d .*(%b())$") or "" -- Exclude any DATE (Phrase) strText = strText:replace(strPhrase,"") strText = strText:gsub(" (%u+)",dicDateWords)..strPhrase -- Convert the DATE words and append any (Phrase) elseif strForm:match("^FromTo") then -- Convert FROM/TO to AFT/BEF -- V4.0 GFT HER strText = strText:gsub("DATE FROM (.+) TO (.+)","DATE from %1 to %2") -- V4.0 strText = strText:gsub("DATE FROM (.-%d%d%d%d)$","DATE AFT %1") -- V4.0 strText = strText:gsub("DATE TO (.-%d%d%d%d)$","DATE BEF %1") -- V4.0 strText = strText:gsub("DATE from (.+) to (.+)","DATE FROM %1 TO %2") -- V4.0 elseif strForm == "Period" then -- Convert Period to Range and remove INT for TribalPages -- V3.2 strText = strText:gsub("DATE FROM (.+) TO (.+)","DATE BET %1 AND %2") strText = strText:gsub("DATE FROM (.+)","DATE AFT %1") strText = strText:gsub("DATE TO (.+)","DATE BEF %1") strText = strText:gsub("DATE INT (.+)","DATE %1") elseif strForm == "Phrase" then -- Convert Phrase to Note -- V4.0 LFT -- local strPhrase = strText:match("^.*(%b())$") -- Extract any (Phrase) -- V3.3 local strPhrase = strText:match("^2 DATE (%b())$") -- Extract (Phrase) only -- V4.0 if strPhrase then tblRecord.Arg = "Original Date" doFactNote((strText:gsub("2 DATE ",""))) -- Add original fact Date to Note -- strText = strText:replace(strPhrase,"") -- Remove date (Phrase) -- V3.3 -- strText = strText:gsub("DATE INT ","DATE ") -- Remove INT prefix -- V3.3 end elseif strForm == "About" then -- Convert Birth/Death Range/Period to simple About -- V3.1 for intPrev = tblRecord.Line-1, 1, -1 do local intLev, strTag = getParts(tblRecord[intPrev]) -- Find previous Birth or Death fact if any if intLev <= 1 then if strTag == "BIRT" or strTag == "DEAT" then for intFind, strFind in ipairs ({"DATE BET";"DATE BEF";"DATE AFT";"DATE FROM";"DATE TO";}) do local strDate = strText:match(strFind.." (.-%d%d%d%d)") if strDate then tblRecord.Arg = "Original Date" doFactNote((strText:gsub("2 DATE ",""))) -- Add original fact Date to Note strText = "2 DATE ABT "..strDate -- Convert first Date to About/Circa -- V3.1 break end end end break end end end if strForm:match("Interpret") then -- Interpret Date (Phrase) to Note and Date -- V4.0 GFT HER MYH local strPhrase = strText:match("^.*(%b())$") -- Extract any (Phrase) -- V4.0 if strPhrase then tblRecord.Arg = "Original Date" doFactNote((strText:gsub("2 DATE ",""))) -- Add original fact Date to Note strText = strText:replace(strPhrase,"") -- Remove date (Phrase) strText = strText:gsub("DATE INT ","DATE ") -- Remove INT prefix end end if strText:length() > intSize then -- Maximum 30 Date chars + 7 chars for level digit & " DATE " -- V2.0 strText = strText:gsub("DATE FROM (.+) TO (.+)","DATE %1 TO %2") strText = strText:gsub("DATE BET (.+) AND (.+)","DATE BET %1 & %2") strText = strText:substring(1,intSize) end return strText end -- local function doDateField local function DateDayNumber(datDate) -- Obtain Date Point & Day Number from Date (from Order Facts by Sort Date) -- V3.5 -- datDate ~ Date value local intDate = nil local dtpDate = datDate:GetDatePt1() local intMon = 12 -- Default to 31st & Dec local intDay = 31 if dtpDate:IsNull() then -- Get earliest Date Point dtpDate = datDate:GetDatePt2() end if not dtpDate:IsNull() then local arrLast = { 31;28;31;30;31;30;31;31;30;31;30;31; } -- Limit last day for current month local intDayNo = dtpDate:GetDay() local intMonth = dtpDate:GetMonth() local intYear = dtpDate:GetYear() local strType = datDate:GetSubtype() if strType == "Before" then intDayNo = intDayNo - 1 -- Before date needs previous day if intDayNo < 1 then intMonth = intMonth - 1 if intMonth < 1 then intYear = intYear - 1 end end intMon = 12 -- Always default to 31 Dec intDay = 31 end if strType == "After" then if intMonth <= 0 then intMonth = 12 end if intDayNo <= 0 then intDayNo = 31 end intDayNo = intDayNo + 1 -- After date needs following day if intDayNo > arrLast[intMonth] then intDayNo = 1 intMonth = intMonth + 1 if intMonth > 12 then intMonth = 1 intYear = intYear + 1 end end end if intMonth <= 0 then intMonth = intMon end -- Set missing month to January or December if intDayNo <= 0 then intDayNo = intDay end -- Set missing day to 1st or last day of month intDayNo = math.min(intDayNo,arrLast[intMonth]) dtpDate:SetValue(intYear,intMonth,intDayNo) intDate = general.GetDayNumber(dtpDate) -- Get its day number -- V3.6 if not intDate then -- Report if Date invalid fhMessageBox("\n Invalid Date Day Number for "..intDayNo.."/"..intMonth.."/"..intYear.." \n Should never get here. \n") end end return dtpDate, intDate -- Return its Date Point & Day Number end -- local function DateDayNumber local function doSortDates(strInit) -- Add a Sort Date to every Fact for GST & RMT -- V3.3 -- strInit ~ Initial letter I/F for parent record local dtpSort = fhNewDatePt() dtpSort:SetValue(1000,1,1) if strInit == "F" then dtpSort:SetValue(3000,1,1) end -- Undated Family facts come last -- V3.4 local intSort = fhCallBuiltInFunction("DayNumber",dtpSort) -- Set initial Sort Date = 1 Jan 1000/3000 and its day number local intPrev = intSort -- Previous explicit Date day number -- V3.4 local intLine = tblRecord.Line + 1 while intLine <= #tblRecord do -- Search record for Facts local strLine = tblRecord[intLine] local intLev, strTag, strVal = getParts(strLine) if intLev == 0 or strLine == "1 chan" then break end local dicTag = doNameTag(strTag) if intLev == 1 and dicTag[strInit] == 0 -- Level 1 INDI/FAM Fact, etc, and strTag ~= "NAME" and strTag ~= "ASSO" then -- but exclude NAME and ASSO if tblRecord[intLine+1]:match("2 TYPE ") then intLine = intLine + 1 -- Cater for 1 EVEN/FACT/_ATTR followed by 2 TYPE tag -- V4.0 end for intNext = intLine+1, #tblRecord do -- Search for any explicit DATE, _SDATE, LMO Sort Date, or labelled NOTE local isLMO = false -- V3.5 local strNext = tblRecord[intNext] -- V4.0 local intLev, strTag, strVal, isLink = getParts(strNext) if intLev < 2 then break end if strNext == "2 OBJE" and tblRecord[intNext+1] == "3 TITL Sort Date" then -- LMO Sort Date? -- V3.5 intLev, strTag, strVal, isLink = getParts(tblRecord[intNext+2]) isLMO = true end if (intLev == 2 and strTag == "DATE") -- Find an explicit Date for the Fact or (intLev == 2 and strTag == "_SDATE") -- Find an explicit Sort Date for Fact -- V4.0 or (intLev == 3 and strTag == "_DATE" and isLMO) -- Find an LMO Sort Date for the Fact -- V3.5 or (intLev == 2 and strTag == "NOTE" and not isLink) or (intLev == 3 and strTag:match("CON[CT]")) then -- Find a labelled Note for the Fact local datDate = fhNewDate() local strDate = strVal:match("Sort Date:[ \t]+(.*%d%d%d%d)") or strVal:gsub("^INT (.+) %b()$","%1") -- Convert interpeted date phrase to plain Date if datDate:SetValueAsText(strDate) and not datDate:IsNull() then -- Date format is OK local dtpDate, intDate = DateDayNumber(datDate) -- So get its Date Point & Day Number -- V3.5 if intDate then -- Date is valid so has a Day Number if intDate > intSort -- Increase Sort Date to explicit Date or intDate < intPrev then -- Decrease Sort Date to explicit Date unless same as previous dtpSort = dtpDate intSort = intDate -- Should = fhCallBuiltInFunction("DayNumber",dtpSort) end intPrev = intDate -- Save previous explicit Date if isLMO then break end -- V3.5 end end end end intLine = intLine + 1 for intNext = intLine, intLine+9 do local intLev, strTag = getParts(tblRecord[intNext]) if intLev == 2 and strTag == "DATE" then -- Ensure Sort Date is after any explicit Date -- V4.0 intLine = intNext + 1 break elseif intLev < 2 then break end end local datSort = fhNewDate() -- Now can insert the Sort Date datSort:SetSimpleDate(dtpSort) local strSort = datSort:GetDisplayText("COMPACT"):upper() -- Cater for Tools > Preferences > General > Date Format mmm dd, yyyy -- V3.4 strSort = strSort:gsub("^(%u%u%u) (%d+), (%d%d%d%d)$","%2 %1 %3") if tblRecord[intLine]:match("^2 _SDATE") then doModify("2 _SDATE "..strSort,intLine) -- V4.0 else doInsert("2 _SDATE "..strSort,intLine) end dtpSort = fhCallBuiltInFunction("CalcDate",dtpSort,0,0,1) -- Add one day to Sort Date for undated Facts end intLine = intLine + 1 end return nil -- Retain original line end -- local function doSortDates local function doMove2LMO(strDate) -- Move Sort Date to LMO form for FH -- V4.0 -- strDate ~ Date text local intLine = tblRecord.Line doInsert("2 OBJE",intLine) -- Create Local Media Object Sort Date doInsert("3 TITL Sort Date",intLine+1) doInsert("3 _DATE "..strDate,intLine+2) return "" -- Delete original line end -- local function doMove2LMO local FSO = luacom.CreateObject("Scripting.FileSystemObject") -- File System Object -- V4.5 local function doCopyFile(strSource,strExport) -- Copy media file and modification date-time; used by doMediaError(), doProcessImage(), doMakeFile() -- strSource ~ Source file path -- strExport ~ Export file path --[=[ local fileSource = assert(io.open(strSource,"rb")) local fileExport = assert(io.open(strExport,"wb")) local anyContent = "" repeat -- Copy entire file contents -- V3.2 fileExport:write(anyContent) anyContent = fileSource:read(10000) until anyContent == nil assert(fileSource:close()) assert(fileExport:close()) --]=] FSO:CopyFile(fhConvertANSItoUTF8(strSource),fhConvertANSItoUTF8(strExport)) -- Copy entire file contents -- V4.5 lfs.touch(strExport,os.time(),lfs.attributes(strSource,"modification")) end -- local function doCopyFile local dicErr = {} -- List of inhibited error messages -- V4.5 local function doMediaError(strErr,getFile,putFile,strSid,strRid,strArea,intH,intW,doCopy) -- V4.0 -- Allow Part Frame Folders -- strErr ~ Custom error message text -- Report a media error message and optionally copy media file -- getFile ~ Full path of source media file -- putFile ~ Full path of target media file -- strSid ~ Media file Seq Id -- strRid ~ Media file Rec Id -- strArea ~ Frame _AREA = "{T,L,B,R}" (if any) -- intH,intW ~ Media image Height & Width (if any) -- doCopy ~ Full image copy required? if strSid ~= "0" then strSid = " "..strMsid..": "..strSid -- Include the _ASID/_SEQ -- V4.0 else strSid = "" end if strArea then strArea = " _AREA: "..strArea -- Include frame _AREA else strArea = "" end local strSize = "" if intH and intW then strSize = "\n\nImage Height: "..intH.." Width: "..intW -- Report media image Height & Width end local strCopy = "\n\nExport image file may not get created" -- No copy required if doCopy then strCopy = "\n\nCopying image file without cropping frame" doCopyFile(getFile,putFile) -- Full image file copy end getFile = getFile:replace(StrPath,"") -- Remove project root folder if possible getFile = encoder.StrANSI_UTF8(getFile) -- V4.0 local _, strFile = general.SplitFilename(putFile) strFile = encoder.StrANSI_UTF8(strFile) -- V4.0 local strMsg = strErr..strSize.."\n\nMultimedia Record Id: OBJE ["..strRid.."]"..strSid..strArea.."\n\nSource: "..getFile.."\n\nExport: "..strFile..strCopy if not dicErr[strErr] then local intKey = iup_gui.MemoDialogue(strMsg.."\n \n Inhibit all similar future messages? \n \n","Yes Inhibit","No Retain") if intKey == 1 then dicErr[strErr] = true end -- Inhibit this error message -- V4.5 progbar.Focus() end setResultSet(0,-2,strOrig,strMsg:gsub("\n+"," ")) -- Update the Result Set -- V4.5 collectgarbage("collect") -- V4.5 end -- local function doMediaError local function getImageError(intErr) -- Obtain Image Error message; used by doProcessImage() --? return im.ErrorStr(intErr) -- Should work but fails in v3.4.2 (FH V5) but OK in v3.8.2 (FH V6) local dicError = { } -- So use lookup dictionary dicError[im.ERR_OPEN] = "Error while opening the file" dicError[im.ERR_ACCESS] = "Error while accessing the file" dicError[im.ERR_FORMAT] = "Invalid or unrecognized file format" dicError[im.ERR_DATA] = "Invalid or unsupported data" dicError[im.ERR_COMPRESS] = "Invalid or unsupported compression" dicError[im.ERR_MEM] = "Insufficient memory" return dicError[intErr] end -- local function getImageError local function doResizeImage(putImage,putH,putW,newH,newW) -- Resize image; Derived from 'Convert File Links' plugin; used by doProcessImage() -- putImage ~ Current image -- putH,putW ~ Current image Height & Width for old image -- newH,newW ~ Maximum image Height & Width for new image local putAspect = putW / putH local newAspect = newW / newH -- Maintain image aspect ratio if putAspect < newAspect then newW = math.floor( newH * putAspect ) -- New height OK, adjust new width elseif putAspect > newAspect then newH = math.floor( newW / putAspect ) -- New width OK, adjust new height end if putH ~= newH or putW ~= newW then -- Create new image with new width & new height using bilinear interpolation --[=[ local newImage = im.ImageCreate(newW,newH,putImage:ColorSpace(),putImage:DataType()) im.ProcessResize(putImage,newImage,1) -- Bilinear interpolation to resize old image into new image -- crashes often!!!! --]=] local isOK, newImage = im.ProcessReduceNew(putImage,newW,newH,1)-- V3.7 putImage:Destroy() putImage = newImage end return putImage end -- local function doResizeImage local function doProcessImage(getFile,putFile,putType,strSid,strRid,intT,intL,intB,intR,maxH,maxW) -- V4.0 -- Allow Part Frame Folders -- getFile ~ Full path of source media file -- Derived from 'Convert File Links' plugin; used by doMakeFile() -- putFile ~ Full path of target media file -- putType ~ File type of target media file -- strSid ~ Media file Seq Id -- strRid ~ Media file Rec Id -- intT,intB ~ Top & Bottom Area co-ordinates -- intL,intR ~ Left & Right Area co-ordinates -- maxH,maxW ~ Max Height & Width for target image local oldFile,oldType = putFile,putType -- Keep original parameters in case of error -- V3.7 local oldT,oldL,oldB,oldR = intT,intL,intB,intR local strArea = "Full-Frame" -- V3.7 if not intT then intT = 0 intL = 0 intB = 99999 intR = 99999 -- Area co-ordinates of full frame -- V3.7 else strArea = ("{%d,%d,%d,%d}"):format(intT,intL,intB,intR) -- Preserve original _AREA co-ordinates for error messages end local getImage, intErr = im.FileImageLoad(getFile) -- Load source image from file and check for errors if intErr and intErr ~= im.ERR_NONE then doMediaError("File Image Load: "..getImageError(intErr),getFile,putFile,strSid,strRid,strArea,nil,nil,"Copy") -- V4.0 -- Allow Part Frame Folders return putFile -- V3.7 end local intH = getImage:Height() -- Obtain source image pixel height and width local intW = getImage:Width() intT = intH - intT -- Top & Bot must be from bottom up instead of top down intB = intH - intB if intT < 0 then intT = 0 end -- Confine the Area co-ordinates within source image if intB < 0 then intB = 0 end if intT >= intH then intT = intH-1 end if intB >= intH then intB = intH-1 end if intL < 0 then intL = 0 end if intR < 0 then intR = 0 end if intL >= intW then intL = intW-1 end if intR >= intW then intR = intW-1 end if intT-intB <= 0 or intR-intL <= 0 then -- Check for Area entirely outside the source image -- V1.4 doMediaError("The face/detail frame area is entirely outside image:",getFile,putFile,strSid,strRid,strArea,intH,intW,"Copy") -- V4.0 -- Allow Part Frame Folders getImage:Destroy() return putFile -- V3.7 end local isOK, putImage = pcall(im.ProcessCropNew,getImage,intL,intR,intB,intT) if isOK then -- Image cropped OK to Area co-ordinates local putH = putImage:Height() local putW = putImage:Width() maxH = maxH or 600 maxW = maxW or 600 if putH > maxH or putW > maxW then -- Image too big so resize needed putImage = doResizeImage(putImage,putH,putW,maxH,maxW) end if TblOption.Jpeg == "ON" -- Use "JPEG" file format for all non-JPEG files -- V1.4 and putType:lower() ~= "jpg" then -- Except .jpg (and .jpeg) that always use JPEG -- V3.7 putFile = putFile:gsub(putType.."$","jpeg") -- Change file type to ".jpeg" putType = "jpeg" end local outType = putType:upper():replace("JPG","JPEG"):replace("TIF","TIFF") local putW = putImage:Width() local putH = putImage:Height() local glData,glForm = putImage:GetOpenGLData() -- Convert output image file to the OpenGL data format -- V3.7 local intErr = im.ERR_NONE if glData and glForm then putImage = im.ImageCreateFromOpenGLData(putW,putH,glForm,glData) intErr = im.FileImageSave(putFile,outType,putImage) -- Save image file with format type matching file type -- V3.7 else intErr = im.ERR_DATA -- GetOpenGLData failure detected -- V4.0 end getImage:Destroy() putImage:Destroy() -- Release image memory, and copy file modification date-time lfs.touch(putFile,os.time(),lfs.attributes(getFile,"modification")) if intErr and intErr ~= im.ERR_NONE then if oldType == putType then -- V3.7 doMediaError("File Image Save: "..getImageError(intErr).."\n\nFile Format: "..outType,getFile,putFile,strSid,strRid,strArea,intH,intW) -- V4.0 -- Allow Part Frame Folders else general.DeleteFile(putFile) local strJpeg = TblOption.Jpeg -- On error try to process file with original file type, etc -- V3.7 TblOption.Jpeg = "OFF" -- But without JPEG format conversion putFile = doProcessImage(getFile,oldFile,oldType,strSid,strRid,oldT,oldL,oldB,oldR,maxH,maxW) -- V4.0 -- Allow Part Frame Folders TblOption.Jpeg = strJpeg if strOrig:match("^%d _?FILE ") then getFile = strOrig end getFile = getFile:replace(StrPath,"") -- Remove project root folder if possible local _, strFile = general.SplitFilename(putFile) setResultSet(0,-3,getFile,encoder.StrANSI_UTF8(strFile)) -- Update the Result Set arrays -- V3.7 -- V4.5 collectgarbage("collect") -- V4.5 end end else -- Report crop error message held in 'putImage' local strArgs = (" L: %d R: %d B: %d T: %d"):format(intL,intR,intB,intT) doMediaError("Process Crop Error: "..putImage.."\n\nArgs"..strArgs,getFile,putFile,strSid,strRid,strArea,intH,intW) -- V4.0 -- Allow Part Frame Folders getImage:Destroy() end return putFile -- V3.7 end -- local function doProcessImage local dicFileType = { bmp=1; gif=1; ico=1; jpeg=1; jpg=1; pcx=1; png=1; tga=1; tif=1; tiff=1; } -- Valid image file types local function doMakeFile(getFile,strSid,strRid,intT,intL,intB,intR) -- Make a converted copy of a Media file; used by doObjectId(), doFileLink() -- V4.0 -- Allow Part Frame Folders -- getFile ~ Source media file relative/absolute path in ANSI -- strSid ~ Media file Seq Id -- strRid ~ Media file Rec Id -- intT,intL ~ Top and Left Area co-ordinates (intT = nil for full frame) -- intB,intR ~ Bottom & Right Area co-ordinates local putPath, putFile, putType local intAtAt = 0 -- V3.3 if #getFile > 0 then -- If relative Media source path then make absolute getFile, intAtAt = getFile:gsub("@@","@") -- V3.3 getFile = getFile:gsub("^[Mm]edia\\",function() return StrPath.."Media\\" end) -- V3.3 [Mm] to cater for Media & media folder name putPath, putFile, putType = general.SplitFilename(getFile) -- V2.9 local intPath = 0 if TblOption.Keep == "ON" then -- V2.9 putPath, intPath = putPath:replace(StrPath.."Media\\",TblOption.Path.."\\Media\\") end if intPath == 0 then putPath = TblOption.Path.."\\" end if not intT then strSid = "0" end -- If no part frame then use full frame Sid -- V4.0 if TblOption.Name == 1 then putFile = putPath..strSid.."O"..strRid.." "..putFile -- Export file is in export folder with key name prefix -- V4.0 elseif TblOption.Name == 2 then putFile = putFile:gsub("(.+)%.","%1 ["..strSid.."].") -- Export file is in export folder with Sid name suffix -- V4.0 putFile = putPath..putFile else putPath = putPath.."["..strSid.."]\\" -- Export file is in export folder with Sid sub-folder -- V4.0 general.MakeFolder(putPath) putFile = putPath..putFile -- Allow Part Frame Folders -- V4.0 end if dicFileType[putType:lower()] -- For valid image file types only -- V4.0 and TblOption.Jpeg == "ON" -- Use "JPEG" file format for all non-JPEG files -- V1.4 and putType:lower() ~= "jpg" then -- Except .jpg (and .jpeg) that always use JPEG -- V3.7 putFile = putFile:gsub(putType.."$","jpeg") -- Change file type to ".jpeg" putType = "jpeg" end end if general.FlgFileExists(getFile) then -- Source media file exists if not general.FlgFileExists(putFile) -- No target file, or modification date-times differ or lfs.attributes(getFile,"modification") ~= lfs.attributes(putFile,"modification") then if ( intT or TblOption.Full == "ON" ) -- Part frames or all files apply -- V3.7 and dicFileType[putType:lower()] then -- Convert, reduce & copy existing image file putFile = doProcessImage(getFile,putFile,putType,strSid,strRid,intT,intL,intB,intR,TblOption.High,TblOption.Wide) -- V1.4 -- V3.7 -- V4.0 -- Allow Part Frame Folders else doCopyFile(getFile,putFile) -- Copy audio/video/word-processor file or image file full frame end end else if #getFile > 0 then -- Report missing source media file local strArea = nil if intT then strArea = ("{%d,%d,%d,%d}"):format(intT,intL,intB,intR) end doMediaError("Skipping missing multimedia file:",getFile,putFile,strSid,strRid,strArea) -- V4.0 -- Allow Part Frame Folders end end if putFile then if intAtAt > 0 then putFile = putFile:gsub("@","@@") -- V3.3 end putFile = encoder.StrANSI_UTF8(putFile) -- Make filename UTF-8 end return putFile -- Return target media file path in UTF-8 end -- local function doMakeFile local function doSaveMseq(arrRecord,strRid) -- Make dictionary of Media _ASID/_SEQ entries; used by doObjectId(), doAnalyse() -- arrRecord ~ List of media record lines -- strRid ~ Media record id local strKey = "0"..strRid -- Initial dictionary key is dummy _ASID/_SEQ number plus Record Id local dicTag = { } dicTag.OBJE = { } -- Dictionary of OBJE Multimedia tags dicTag.OBJE._FILE = { } -- Ensure empty _FILE tag entry exists dicTag.ASID = { } -- Dictionary of per _ASID/_SEQ image tags local intTop = 1 -- V2.3 local strTop = "" local arrTag = dicTag.OBJE -- Initially use OBJE dictionary table.insert(arrRecord,"1 CHAN") -- Add CHANge tag in case none exists -- V2.1 for intLine = 2, #arrRecord do local strLine = doPrune(arrRecord[intLine]) -- V1.9 local intLev, strTag, strVal = getParts(strLine) -- V2.0 intLev & getParts if IntFhVersion > 6 then -- V4.0 fix to make FH V7 5.5.1 tags like FH V6 tags if strTag == "TITL" or strTag == "FORM" then intLev = 1 elseif strTag == "NOTE" or strTag == "_NOTA" then strTag = "_NOTE" elseif strTag == "_SEQ" then strTag = "" end end if strTag:match("_?FILE") then if strVal == "" then strVal = nil end -- No file link so exclude dictionary entry -- V4.0 strTag = "_FILE" -- File > Export > GEDCOM File can use FILE, so convert to _FILE -- V4.0 elseif strTag == "_ASID" then intTop = intLev strKey = strVal..strRid -- Dictionary key is _ASID/_SEQ number plus Record Id elseif ( strTag == "NOTE" and intLev == 1 and not strVal:match("^@N%d+@$") ) -- Link/Note, not Note Record link? or intLine == #arrRecord then -- End of OBJE record? -- V2.1 intTop = intLev dicObje[strKey] = { } -- Make dictionary entry for prior ASID per Record Id dicObje[strKey].OBJE = dicTag.OBJE dicObje[strKey].ASID = dicTag.ASID if strKey ~= "0O0" then -- Let LMO synthesised Media record have multiple Link/Note instances -- V2.5 dicTag.ASID = { } -- Clear per ASID image tags for next Link/Note strKey = strRid -- Blank ASID key in case no _ASID/_SEQ tag -- V3.3 end arrTag = dicTag.ASID -- Now use ASID dictionary end if intLev <= intTop then -- V2.3 strTop = strTag if not arrTag[strTop] then arrTag[strTop] = { } end -- Save the tag level & value for each instance of tag end if strTag == "FORM" then intLev = intLev + tblGedSelect[strGedExport].Lev strVal = strVal:gsub(".+",tblGedSelect[strGedExport].Type) -- Correct FORM jpg/jpeg/tif/tiff type -- V4.0 elseif strTag == "TITL" and not isLocObje then intLev = intLev + tblGedSelect[strGedExport].Lev end if IntFhVersion > 6 then -- V4.0 fix to make FH V7 5.5.1 tags like 5.5 tags if strTag == "_NOTE" then strTag = "NOTE" end end if #strTag > 1 then table.insert(arrTag[strTop],{Lev=intLev;Tag=strTag;Val=strVal;}) -- V1.9 -- V2.0 Lev=intLev -- V2.3 Tag=strTag end end end -- local function doSaveMseq local function setObjectUsed(strRid) -- Set Media Object details as Used -- V3.3 -- strRid ~ Numerical part of record id if strRid then local strKey = "0O"..strRid -- Use the Sid=0 & Rid key to set Media Object details as Used -- V3.3 if not dicObje[strKey] then dicObje[strKey] = {} end dicObje[strKey].Used = true -- Indicator when deciding whether to delete OBJE Media records -- V3.3 end end -- local function setObjectUsed local function doMediaSeqId(intLine,intLevel,strVal) -- Synthesise dicObje[strKey].ASID entry -- V4.0 -- intLine ~ Line number of _ASID/_SEQ following OBJE line -- intLevel ~ Level number of OBJE line -- strVal ~ Link to OBJE record local strRiD = strVal:match("^ ?@O(%d+)@$") -- Extract link Record Id if it exists local strLine = tblRecord[intLine] or "" local strSid = strLine:match("^%d "..strMsid.." (%d+)") or "0" -- strMsid is _ASID or _SEQ -- V4.0 local strRid = strRiD or "0" local strKey = strSid.."O"..strRid -- Use the Sid & Rid key to lookup Media Object details local tblObje = dicObje[strKey] if IntFhVersion > 6 and not strAbbr:match("GST") then if tblObje and strRiD then -- Ensure each ASID entry is unique as _SEQ numbers can be replicated -- V4.0 repeat strSid = tostring( tonumber(strSid) + 1 ) strKey = strSid.."O"..strRid tblObje = dicObje[strKey] until not tblObje end if not tblObje and strRiD then -- Synthesise dicObje[strKey] entry -- V4.0 dicObje[strKey] = { OBJE={ _FILE={}; }; ASID={ _ASID={}; }; }-- Create blank OBJE & ASID entries -- V4.0 table.insert( dicObje[strKey].ASID._ASID, { Lev=2; Val=strSid; Tag="_ASID"; } ) if dicObje["0O"..strRid] then for strTag, strVal in pairs ( dicObje["0O"..strRid].OBJE ) do dicObje[strKey].OBJE[strTag] = strVal -- Copy OBJE details from full image -- V4.0 end end tblObje = dicObje[strKey] for intNext = intLine, #tblRecord do -- Synthesise Link/Note ASID table items -- V4.0 local strNext = tblRecord[intNext] local intLev, strTag, strVal = getParts(strNext) if intLev <= intLevel then break end if strTag == "_AREA" or strTag == "_CAPT" or strTag == "_EXCL" then tblObje.ASID[strTag] = {} table.insert( tblObje.ASID[strTag], { Lev=2; Val=strVal; Tag=strTag; } ) elseif strTag == "_NOTA" then strTag = "NOTE" tblObje.ASID[strTag] = {} table.insert( tblObje.ASID[strTag], { Lev=1; Val=strVal; Tag=strTag; } ) elseif strTag:match("CON[CT]") then table.insert( tblObje.ASID["NOTE"], { Lev=2; Val=strVal; Tag=strTag; } ) end end end end return strSid, strRiD, strRid, strKey, tblObje end -- local function doMediaSeqId local function doObjectFormat(intLev,intTag,strTag,strVal,isRec) -- Adjust media TITL level and FORM level & value jpg/jpeg/tif/tiff -- intLev ~ Level number of FILE tag in media -- intTag ~ Level number of TITL or FORM -- strTag ~ "TITL" or "FORM" -- strVal ~ Value of tag item -- isRec ~ true in Media record or false in LMO local isNew = false local intNew = intLev if strTag == "FORM" or isRec then -- Adjust FORM level, or TITL level within Media Record intNew = intLev + tblGedSelect[strGedExport].Lev isNew = ( intNew ~= intTag ) end local strLev = tostring(intNew) if strTag == "FORM" then -- Adjust FORM value local strOld = strVal strVal = strVal:gsub("^.+$",tblGedSelect[strGedExport].Type) isNew = isNew or ( strOld ~= strVal ) end return strLev.." "..strTag.." "..strVal, isNew end -- local function doObjectFormat local function doObjectId(strLev,strVal) -- Media OBJE Conversion -- strLev ~ Level of OBJE line -- strVal ~ Value of OBJE line if isWipeAll then return doRemoveAll(strLev) end -- Remove Media Object for WIPE~ALL -- V1.8 local intLine = tblRecord.Line + 1 local strLine = tblRecord[intLine] or "" if strLine == "3 TITL Sort Date" then return doRemoveAll(strLev) end -- Remove LMO Sort Date -- V3.5 local intLev = tonumber(strLev) local dicArg = tblRecord.Arg local strArg = dicArg.Lab local strLab = dicLabel[strArg] or strArg..":\t" -- Label for Caption Note -- V3.3 local strInit = dicArg.Init -- Initial letter for Media Record Id if not "O" local strMode = dicArg.Mode -- Mode is "Keep" to retain link, "LMO" to insert Preferred Media tag(s) before ***~LMO OBJE, "ABS" before ***~ABS OBJE, "All" for all, otherwise "Tag" to replace OBJE tag local strPref = dicArg.Pref -- Preferred Media tag(s) local strWord = dicArg.Word -- Keyword to filter Object Records -- V3.3 local intT, intL, intB, intR local strSid, strRiD, strRid, strKey, tblObje = doMediaSeqId(intLine,tonumber(strLev),strVal) -- V4.0 if strMode == "Keep" and ( isFileRel or isFileAbs ) then -- Retain original Object Media link (for FH5, FH6 & GST) -- V3.3 if IntFhVersion > 6 and strAbbr:match("FH[56]") then if strRid == "0" then for intLine = intLine, #tblRecord do -- For FH V7 to FH5 & FH6 set level for FORM tag -- V4.0 local strLine = tblRecord[intLine] local intNow, strTag, strVal = getParts(tblRecord[intLine]) if intNow <= intLev then break end if strTag == "FORM" then doModify(tostring(intLev+1).." FORM "..strVal,intLine) break end end elseif strSid ~= "0" then doModify((strLine:gsub(" _SEQ %d+"," _ASID "..strSid)),intLine) -- For FH V7 to FH5 & FH6 convert _SEQ to _ASID -- V4.0 doRemoveAll(strLev,intLine) -- Remove subsidiary _AREA & _EXCL & _CAPT tags -- V4.0 end end return strLev.." OBJE"..strVal end -- # if strSid ~= "0" then doDelete(intLine) end -- Delete _ASID/_SEQ line if it exists -- V1.8 if strLine:match("^%d "..strMsid) then doDelete(intLine) end -- Delete _ASID/_SEQ line if it exists -- V1.8 -- V4.1 if strWord and tblObje then strWord = "^ *"..strWord.." *$" -- Keyword pattern -- V3.3 local strKeys = "" local tblKeys = tblObje.OBJE._KEYS if tblKeys then strKeys = tblKeys[1].Val end -- Get the Keywords -- V3.3 local arrKeys = strKeys:split(",") for intKeys = 1, #arrKeys do if arrKeys[intKeys]:match(strWord) then -- If Keyword exists then keep Media Object -- V3.3 strKeys = strWord break end end if strKeys ~= strWord then return doRemoveAll(strLev) -- Otherwise remove Media Object -- V3.3 end end local dicMediaTags = { "FILE"; "FORM"; "TITL"; "_NOTE"; "_DATE"; "_KEYS"; "NOTE"; -- OBJE general tags -- V2.3 removed "CONC"; "CONT"; -- V2.6 "NOTE" -- V4.0 "FILE" 1st "ASID-NOTE"; "SOUR"; "_ASID"; "_AREA"; -- ASID specific tags -- V2.3 removed "CONC"; "CONT"; "_LINK"; -- V2.6 "ASID-NOTE" } local function strCreateMedia() -- Create Local Media Object or Media Record File Link if tblObje.Link then return tblObje.Link end -- Media Record File Link already exists for duplicates -- V2.0 local putFile, strForm if tblObje then if #tblObje.OBJE._FILE > 0 then local getFile = tblObje.OBJE._FILE[1].Val -- Obtain source Media _FILE details if getFile then getFile = encoder.StrUTF8_ANSI(getFile) -- V1.6 FH5 & FH6 putFile = doMakeFile(getFile,strSid,strRid,intT,intL,intB,intR) -- Copy source Media file to target using Sid, Rid & Frame Area -- V4.0 -- Allow Part Frame Folders if putFile:match("jpeg$") and tblObje.OBJE.FORM then strForm = tblObje.OBJE.FORM[1].Val tblObje.OBJE.FORM[1].Val = "jpeg" -- Adjust FORMat to match as type may have changed -- V3.2 end end end end local strTitle = "" if putFile then strTitle = "" -- Default missing media TITLe tblObje.OBJE.FILE = {} table.insert(tblObje.OBJE.FILE,{Lev=1;Tag="FILE";Val=putFile;}) -- Create new media 1 FILE link -- V2.3 Tag= end if not tblObje.OBJE.TITL then -- Create missing media 1 TITLe tblObje.OBJE.TITL = {} table.insert(tblObje.OBJE.TITL,{Lev=1;Tag="TITL";Val=strTitle;}) -- V2.3 Tag= end local intLev = 0 local strVal = strVal if isLocObje then intLev = tonumber(strLev) -- Create a Local Media Object strVal = "" else if not (intT or tblObje.ASID.NOTE) and #strVal > 0 then -- Use existing Full frame Media Record Id -- V3.3 local strRid = (strInit or "O")..strRid -- V3.3 if strVal == " @"..strRid.."@" then tblObje.Used = true -- Indicator when deciding whether to delete OBJE Media records -- V3.3 end return strVal end intObje = intObje + 1 strVal = " @O"..intObje.."@" -- Create new Part frame Media Record Id if strInit then strVal = strVal:gsub("O",strInit) end -- For FTL/FTM change @O...@ to @M...@ -- V1.8 -- V2.0 tblObje.Link = strVal -- Save this link in case of duplicates -- V2.0 doInsert("0"..strVal.." OBJE") end local isLinkNote = false if isAllType and tblObje.ASID.NOTE then -- Link/Note -- V2.5 -- V3.3 for intNote = 1, #tblObje.ASID.NOTE do if #tblObje.ASID.NOTE[intNote].Val > 0 then isLinkNote = true -- With Link/Note text -- V2.5 break end end end local dicTag = tblObje.OBJE -- Tags based on Media Record details in tblObje.OBJE & ASID for intTag, strTag in ipairs (dicMediaTags) do if strTag == "ASID-NOTE" then -- Use ASID specific tags for NOTE, CONT, SOUR -- V2.6 strTag = "NOTE" dicTag = tblObje.ASID end if strInit and not isLocObje and strTag == "SOUR" then break end -- For FTL/FTM exclude most ASID specific tags from synthetic media records if dicTag[strTag] -- Add tag line only if a value is available and not (strTag == "_NOTE" and isLinkNote) then -- and not Picture Note suppressed by Link/Note -- V2.5 for intTag, arrTag in ipairs (dicTag[strTag]) do -- Find each instance of same tag local intLev = arrTag.Lev + intLev -- Offset level relative to original OBJE link tag -- V2.0 local strNew = intLev.." "..arrTag.Tag.." "..arrTag.Val -- V2.3 -- V2.5 intTag -> intLev local intLev, strCon, strVal, isLink = getParts(strNew) if strTag == "NOTE" then if not isLink and strCon ~= "CONC" and #strVal > 0 then -- Add Caption label to NOTE/CONT unless no text -- V3.3 strNew = strNew:gsub("^(%d %u+ )","%1"..strLab) -- V2.5 -- Add Label -- V3.3 end local arrCont = dicTag["NOTE"][intTag+1] or {Tag=""} local arrSour = dicTag["SOUR"] if #strVal == 0 and not ( arrSour or arrCont.Tag:match("^CON[CT]") ) then -- V2.5 -- V3.3 strNew = "" -- Remove blank Link/Note unless a Source Citation or Continuation follows end end if #strNew > 0 then if isLocObje then doInsert(strNew,intLine) -- Add new Local Media Object tag line intLine = intLine + 1 else doInsert(strNew) -- Add new Media Record tag line end end end end end if strForm then tblObje.OBJE.FORM[1].Val = strForm -- Restore temporarily adjusted Format -- V3.2 end return strVal -- Media link or empty string for LMO end -- local function strCreateMedia --[=[ Example MYH Gedcom for preferred image Personal Cutout Position 1 OBJE 2 FORM jpeg 2 TITL Ian and Charlotte and family 2 FILE C:\Users\Mike\OneDrive\Documents\Family Historian Projects\Test Export Gedcom File\Public\Export\6O12 IMG_3194.JPG 2 NOTE Media Date: 1 MAY 2005 2 _PRIM Y 2 _PRIM_CUTOUT Y 2 _POSITION 0 0 164 214 1 OBJE 2 FORM jpeg 2 TITL Ian and Charlotte and family 2 FILE C:\Users\Mike\OneDrive\Documents\Family Historian Projects\Test Export Gedcom File\Public\Export\6O12 IMG_3194.JPG 2 _PRIM Y 2 _CUTOUT Y --]=] local function strCreateObject(strLev,strVal,intLnk) -- Create Local Media Object or Media Record Link -- V1.8 -- V2.0 -- V2.1 local strTag = " OBJE" if strPref and strLev == "1" and isRecord("INDI") and not tblRecord[strPref] then local tblExcl = ((tblObje or {""}).ASID or {""})._EXCL or {""} -- V3.3 local strExcl = tblExcl[1].Val -- 'Exclude from Diagrams' inhibits preferred image -- V2.9 if strExcl ~= "ALL" and strExcl ~= "DGM" then if strMode == "All" then -- All Local Media Objects and ABS Media Links -- V3.2 tblRecord[strPref] = true -- TNG adds _PRIM Y tag for preferred INDIvidual image -- V2.1 doInsert("2"..strPref,intLnk) intLine = intLine + 1 elseif #strVal == 0 then -- Local Media Object -- V3.2 Repair if strMode == "LMO" and tblObje then -- V3.3 for intLink = intLnk, #tblRecord do -- Locate end of Media Object local intLev = getParts(tblRecord[intLink]) if intLev <= 1 then -- Add tags to identify preferred INDIvidual image -- V3.2 tblRecord[strPref] = true if strPref:match("_POSITION") then -- Set the MYH Cutout Position width & height to image width & height -- V3.2 local dicObje = tblObje.OBJE local strFile = encoder.StrUTF8_ANSI(dicObje.FILE[1].Val) -- V4.0 local putImage, intErr = im.FileImageLoad(strFile) -- V4.0 -- # local putImage, intErr = im.FileImageLoad(dicObje.FILE[1].Val) if not putImage then break end -- Abort if not an image file local putH = putImage:Height() local putW = putImage:Width() strPref = strPref:gsub("{W}",putW):gsub("{H}",putH) putImage:Destroy() -- Set the MYH file Size, Format, Title, and Name values -- V3.2 strPref = strPref:replace("{S}",lfs.attributes(strFile,"size")) -- V4.0 --# strPref = strPref:replace("{S}",lfs.attributes(dicObje.FILE[1].Val,"size")) strPref = strPref:replace("{F}",dicObje.FORM[1].Val) strPref = strPref:replace("{T}",dicObje.TITL[1].Val) -- Cater for % in Title -- V4.5 strPref = strPref:replace("{N}",dicObje.FILE[1].Val) end local arrPref = strPref:split("\n") -- AQP, LFT, RMT _PRIM Y only for _, strPref in ipairs (arrPref) do -- MYH adds _PRIM Y, _PRIM_CUTOUT Y, _POSITION 0 0 {W} {H}(, _FILESIZE {S}), OBJE, FORM {F}, TITL {T}, FILE {N}, _PRIM Y, _CUTOUT Y -- V3.2 local strLev = "2" if strPref:match("OBJE") then strLev = "1" end doInsert(strLev..strPref,intLink) intLink = intLink + 1 intLine = intLine + 1 end break end end end else -- ABS Media Link if strMode == "ABS" then tblRecord[strPref] = true doInsert(strLev..strPref..strVal,intLnk) -- FMP uses _PROF tag as well as OBJE for preferred INDIvidual image -- V3.3 intLnk = intLnk + 1 elseif strMode == "Tag" then tblRecord[strPref] = true strTag = strPref -- FTL/FTM uses _PHOTO tag instead of OBJE for preferred INDIvidual image -- V2.0 -- V2.1 end end end end doInsert(strLev..strTag..strVal,intLnk,nil,true) -- Level digit & tag & link -- V2.1 -- V4.0 intLine = intLine + 1 end -- local function strCreateObject if strInit then strVal = strVal:gsub("O",strInit) end -- For FTL/FTM change @O...@ to @M...@ -- V1.8 -- V2.0 if isLocObje and not strRiD then -- Already LMO for ****~LMO -- V1.8 -- V3.3 local isFormOK = false local strForm = "" local isNew = false while intLine < #tblRecord do -- Correct level & value for FORM tag -- V4.0 local strLine = tblRecord[intLine] local intNow, strTag, strVal = getParts(tblRecord[intLine]) if intNow <= intLev then break end if strTag == "FILE" then if isFormOK then doInsert(strForm,intLine+1) break -- FILE after FORM so insert FORM after FILE else isFormOK = true end -- FILE before FORM elseif strTag == "FORM" then -- Should cater for MEDI subsidiary of FORM in LMO here ???????????????? strForm, isNew = doObjectFormat(intLev+1,intNow,"FORM",strVal,false) -- V4.0 if isNew then -- Correct FORM level & jpg/jpeg/tif/tiff type -- V4.0 if isFormOK then doModify(strForm,intLine) break -- FORM after FILE so modify FORM here else isFormOK = true doDelete(intLine) -- FORM before FILE so delete FORM and wait for FILE intLine = intLine - 1 end else break end end intLine = intLine + 1 end return strLev.." OBJE"..strVal -- Retain Media Object end if isFileRel or isFileAbs then -- FILE~REL or FILE~ABS -- V3.3 setObjectUsed(strRiD) -- Use the Sid=0 & Rid key to set Media Object details as Used -- V3.3 strCreateObject(strLev,strVal,tblRecord.Line) -- Retain Media Object for FILE~REL or FILE~ABS -- V1.8 -- V3.3 return "" -- Delete original line -- V3.3 end local intLnk = intLine -- V2.1 local isUseFull = true -- V3.3 if tblObje and isNotFull then -- Sid & Rid key matches Media Object ASID and Part Frame needed -- V1.8 if tblObje.ASID._AREA then local strArea = tblObje.ASID._AREA[1].Val or "{}" -- Image frame _AREA co-ordinates local tblArea = strArea:match("{(.*)}"):splitnumbers() -- {Top,Left,Bottom,Right} -- V1.4 intT, intL = tblArea[1], tblArea[2] intB, intR = tblArea[3], tblArea[4] end if intT or (tblObje.ASID.NOTE and #tblObje.ASID.NOTE[1].Val>0) then -- Create Local Media Object or Media Record for image part frame or Caption -- V3.3 isUseFull = isAllType -- V3.3 local strLnk = strCreateMedia() if isAllType then -- V1.8 if isLocObje then -- Add OBJE link for part frame Local Media Object strCreateObject(strLev,strLnk,intLnk) -- V2.1 else -- Add OBJE link for part frame Media Record prior to full frame link strCreateObject(strLev,strLnk,tblRecord.Line) -- V2.1 end else strVal = strLnk end end end if isUseFull then -- PART~*** with no image part frame nor Link/Note, or FULL~***, or ALL~*** -- V1.8 -- V3.3 intT = nil strKey = "0O"..strRid -- Use full Sid & Rid key to lookup Media Object details if not isLocObje or isAllType then -- Full Local Media Object uses Part Frame specific Link/Note -- V2.5 local arrExcl if tblObje then -- V3.2 Repair arrExcl = tblObje.ASID._EXCL -- and its Exclude options -- V3.2 end tblObje = dicObje[strKey] if tblObje then -- V3.2 Repair tblObje.ASID._EXCL = arrExcl end end if strRid == "0" then -- Create a new Media Record from Local Media Object local arrRecd = {"0 @O0@ OBJE"} local intLev = tonumber(strLev) while intLine <= #tblRecord do -- Synthesise an old Media Record from Local Media Object local strLine = tblRecord[intLine] local intLin = getParts(strLine) if intLin <= intLev then break end strLine = strLine:gsub("^%d+",tostring(intLin-intLev)) -- Adjust level number for synthetic Media Record strLine = strLine:gsub("^1 FILE","1 _FILE") -- Adjust FILE to _FILE for synthetic Media Record table.insert(arrRecd,strLine) -- Move lines from Local Media to synthetic Media Record doDelete(intLine) end doSaveMseq(arrRecd,"O0") -- Submit synthetic Media Record to create dicObje["0O0"] entry tblObje = dicObje["0O0"] dicObje["0O0"] = nil -- Remove temporary dicObje["0O0"] entry end intLnk = intLine if tblObje then strVal = strCreateMedia() -- Create Local Media Object or Media Record for full image end end if isLocObje then -- Convert original OBJE line to Local Media Object or Media Record Link -- V2.1 strCreateObject(strLev,strVal,intLnk) else strCreateObject(strLev,strVal,tblRecord.Line) end return "" -- Delete original line -- V2.1 end -- local function doObjectId local function doFileLink(strLev,strFile) -- Process media _FILE or FILE link -- strLev ~ Level of FILE line -- strFile ~ File path local getFile = encoder.StrUTF8_ANSI(strFile) local intLine = tblRecord.Line local anyArg = tblRecord.Arg if type(anyArg) == "string" then -- FMP Keyword "FindMyPast" -- V3.3 -- V3.8 local intLev = tonumber(strLev) local intFile = intLine -- Remember FILE line position for intLine = intFile+1, #tblRecord, 1 do -- Search forward for Keywords -- V3.3 local strLine = tblRecord[intLine] local intGet, strTag, strVal = getParts(tblRecord[intLine]) if intGet < intLev then break end if strTag == "_KEYS" then local strWord = "^ *"..anyArg.." *$" -- Keyword to invoke Zip Media -- V3.3 local arrKeys = strVal:split(",") for intKeys = 1, #arrKeys do if arrKeys[intKeys]:match(strWord) then -- If Keyword exists then Zip Media File -- V3.3 local putPath, putFile, putType = general.SplitFilename(getFile) local strTitle = "" for intLine = intFile+4, 1, -1 do -- Search back for Title -- V3.3 local strLine = tblRecord[intLine] local intGet, strTag, strVal = getParts(tblRecord[intLine]) if intGet < intLev then break end if strTag == "TITL" then strTitle = strVal -- Remember the whole Media Title for FULL~ABS, FILE~REL, FILE~ABS if not isCaption then local strPref = putFile:match("^(%d+O%d+ )") or "0O0 " strTitle = strPref..strTitle -- Make part Media Title/File unique for PART~ABS, ALL~ABS, and ***~LMO end strTitle = strTitle.."."..putType:lower() -- Revised Title = Filename -- V3.3 doModify(strLev.." TITL "..strTitle,intLine) doInsert(strLev.." _ASTTYP 1",intLine+1) -- _ASTTYP 1 and _ASTPERM 4 help to match zip files -- V3.3 for intNext = intLine+1, #tblRecord+1 do local intNxt, strTag, strVal = getParts(tblRecord[intNext]) if intNxt < intLev or strTag:upper() == "CHAN" then -- Ensure _ASTPERM comes after any NOTE -- V3.3 doInsert(tostring(intLev-1).." _ASTPERM 4",intNext) break -- See do_ASTPERM() below that turns NOTE into _ASTDESC -- V3.3to end end elseif strTag == "FORM" then doModify(strLev.." FORM JPG",intLine) -- Helps to match zip files, regardless of actual file type break end end general.MakeFolder(StrZipFMP,iup_gui.MemoDialogue) putFile = StrZipFMP..strTitle -- File that needs to be zipped -- V3.3 if not general.FlgFileExists(putFile) -- No target file, or modification date-times differ or lfs.attributes(getFile,"modification") ~= lfs.attributes(putFile,"modification") then doCopyFile(getFile,putFile) -- Full file copy end return strLev.." FILE "..strTitle -- Revise FILE name -- V3.3 end end end end for intLine = intLine-1, 1, -1 do -- Unused object to be deleted local intGet = getParts(tblRecord[intLine]) doDelete(intLine) if intGet < intLev then break end end doRemoveAll(tostring(intLev-1)) -- Unused object to be deleted return "" -- Delete original line end -- type(anyArg) == "string" if type(anyArg) == "table" then -- V4.0 removed: and not tblRecord[intLine-1]:match(strArg) local getType = getFile:match(".+%.(.-)$") -- Check file type for image or other format -- V3.8 if dicFileType[getType:lower()] then doInsert(strLev..anyArg[1]..anyArg[2],intLine) -- Insert mandatory tag such as _TYPE PHOTO for Ancestral Quest -- V3.8 else doInsert(strLev..anyArg[1]..anyArg[3],intLine) -- Insert mandatory tag such as _TYPE OTHER for Ancestral Quest -- V3.8 end end -- type(anyArg) == "table" if isRecord("HEAD") -- Exclude 1 FILE in HEAD record -- # or getFile:match(TblOption.Path:plain()..".*\\%d+O%d+ ") then -- Link to converted Media file needs no processing -- V2.9 or getFile:match(TblOption.Path:plain()) then -- Link to converted Media file needs no processing -- V2.9 -- V4.0 return nil -- Retain original line end if isFileRel then -- FILE~REL -- V1.8 getFile = getFile:gsub("^media\\","Media\\") -- Ensure Media folder name capitalised -- V4.0 getFile = encoder.StrANSI_UTF8(getFile) -- Make relative Media folder path UTF-8 -- V1.8 elseif isFileAbs then -- FILE~ABS getFile = getFile:gsub("^[Mm]edia\\",function() return StrPath.."Media\\" end) -- V3.3 [Mm] to cater for Media & media folder name -- Convert .tif into .jpg for GedSite? Not needed, but it was contemplated to go here. -- getFile = encoder.StrANSI_UTF8(getFile) -- Make absolute Media folder path UTF-8 -- V3.1 else local strRid = tblRecord[1]:match("^0 @%u(%d+)@ OBJE") or "0" -- Obtain Multimedia Record Id if it exists getFile = doMakeFile(getFile,"0",strRid) -- Copy source Media file to target using Sid=0 & Rid -- V4.0 -- Allow Part Frame Folders end return strLev.." FILE "..getFile -- Replace _FILE with FILE end -- local function doFileLink local function do_ASTPERM(strLev) -- Adjust _ASTPERM and convert local NOTE to _ASTDESC -- V3.3 -- strLev ~ Level of current line local intLev = tonumber(strLev) + 1 for intLine = tblRecord.Line-1, 1, -1 do local intGet, strTag, strVal, isLink = getParts(tblRecord[intLine]) if intGet < intLev then break end if strTag == "NOTE" and not isLink then doModify(tostring(intGet).." _ASTDESC "..strVal,intLine) break end end return intLev.." _ASTPERM 4" end -- local function do_ASTPERM local function doKeyword(strLev,strKeys) -- Process media _KEYS Keywords -- V2.1 -- strLev ~ Level of _KEYS line -- strKeys ~ Value of _KEYS line local strTag = tblRecord.Arg[2] -- Extract Note label and Keyword tag -- V3.3 local strLab = tblRecord.Arg[1] tblRecord.Arg = strLab or "" --# doLocNote(strLev,strKeys) -- Create local labelled Note -- V4.5 moved below if strTag then local arrKeys = strKeys:split(",") for intKeys = 1, #arrKeys do local strKey = arrKeys[intKeys]:match("^ *(%u+) *$") -- Find first all upper-case Keyword -- V2.2 if strKey then if #arrKeys > 1 then -- Create local labelled Note if more than one Keywords -- V4.0 -- V4.5 strKeys = strKeys:gsub(strKey,"") -- Remove the all upper-case Keyword -- V4.5 doLocNote(strLev,strKeys) end return strLev..strTag..strKey -- Create TNG _TYPE code keyword end end end doLocNote(strLev,strKeys) -- Create local labelled Note if no upper-case Keywords -- V4.0 -- V4.5 return "" -- Delete original line end -- local function doKeyword local function doLocDate(strText) -- Process media object date -- strText ~ Date text strText = doDateField(strText) return doLocNote(strText:match("^(%d) _DATE (.*)")) end -- local function doLocDate local function doObjFormat(strLev,strTag,strVal) -- Convert/remove Object Format/Title tag -- V2.8 -- V4.0 -- strLev ~ Level of FORM/TITL line -- strTag ~ Tag code FORM/TITL -- strVal ~ Value of FORM/TITL line local intLev = tonumber(strLev) local intLine = tblRecord.Line local strArg = tblRecord.Arg if isRecord("OBJE") and strArg == "5.5" then -- Correct the TITL & FORM level & value jpg/jpeg/tif/tiff for GEDCOM 5.5 -- V4.0 return doObjectFormat(1,intLev,strTag,strVal,true) end if isRecord("OBJE") or ( strTag ~= "TITL" and ( tblRecord[intLine-1]:match("^%d OBJE$") or tblRecord[intLine-2]:match("^%d OBJE$") ) ) then -- Cater for _PRIM Y after OBJE -- V3.3 -- V3.8 if not strArg then return "" end -- Remove original FORM/TITL tag when no Arg -- V3.8 local strNxt = nil if strTag == "FORM" then -- V4.0 moved local intNxt, strTag, strDat = getParts(tblRecord[intLine+1]) if intNxt == intLev+1 and ( strTag == "MEDI" or strTag == "TYPE" ) then strNxt = " "..strTag.." "..strDat -- 5.5.1 tags MEDI (in LMO) or TYPE (in Rec) after FORM tag -- V2.8 end end for intNext = intLine-2, #tblRecord do -- Search for _FILE or FILE tag local intNxt, strAny, strDat = getParts(tblRecord[intNext]) if intNxt < intLev and intNext > intLine then break end if strAny:match("^_?FILE$") then -- Move FORM/TITL to subsidiary of _FILE or FILE tag for 5.5.1 local strLine = doObjectFormat(intNxt,intLev,strTag,strVal,true) -- V4.0 doInsert(strLine,intNext+1) if strNxt then doInsert(tostring(intLev+2)..strNxt,intNext+2) -- V2.8 doDelete(intLine+1) end return "" -- Delete original line end end end return nil -- Retain original line end -- local function doObjFormat local function doObjNote(strLev) -- Link to Note Record for Media tags -- strLev ~ Level of current line local intLev = tonumber(strLev) local intLine = tblRecord.Line for intPrev = intLine-1, 1, -1 do -- Search previous Media lines local intPrv, strTag, strVal = getParts(tblRecord[intPrev]) if intPrv <= intLev then if strTag == "TITL" or strTag == "FORM" or strTag == "OBJE" then intNote = intNote + 1 -- Create a Note Record using next available Note Record Id doInsert("0 @N"..intNote.."@ NOTE "..strPrefix.."Media Object: "..strVal) -- V3.3 break -- Stop search when valid tag found that must at least be "OBJE" end end end local intNext = intLine while intNext <= #tblRecord do -- Search subsequent Media lines local intNxt, strTag, strVal, isLink = getParts(tblRecord[intNext]) if intNxt >= intLev then if strTag == "SOUR" then doInsert("1 SOUR "..strVal) -- Move Source Note/Link to Note Record intNext = doDelete(intNext) -- Delete original line -- V1.8 elseif not isLink and doNameTag(strTag).M then -- Move valid Media tag to Note Record -- V1.9 -- V2.3 cater for UDF tag local strLab = "" local dicRule = dicRule[" "..strTag] or dicRule[" _"..strTag] if dicRule then strLab = dicRule.Arg..":\t" end -- Lookup label argument from rules local strCon = "CONT " -- Convert line into Note Record Continuation line with appropriate label if strTag == "_DATE" then strVal = doDateField(strVal) -- Format Date field elseif strTag == "CONC" then strCon = "CONC " end -- Concatenation line doInsert("1 "..strCon..strLab..strVal) -- Create the CONT/CONC line intNext = doDelete(intNext) -- Delete original line -- V1.8 else intNext = intNext + 1 end else break -- Stop search when higher level reached end end return strLev.." NOTE @N"..intNote.."@" -- Replace original line with Note Record link end -- local function doObjNote local function doObj_Nota(strLev) -- Handle _NOTA annotations -- V4.0 -- strLev ~ Level of _NOTA line if isRecord("OBJE") then return doObjNote(strLev) -- Within a Media record use a linked Note record end return doRemoveAll(strLev) -- Otherwise remove entirely end -- local function doObj_Nota local function doLinkNote(strInit,strRid,arrArg) -- Link any Record to associated Note Records; used by doRecordRule(), addLinkRecRule() -- strInit ~ Record Id initial letter -- strRid ~ Record Id numerical digits -- arrArg ~ List of Note Record Id local intLine = tblRecord.Line for intArg, intNote in ipairs ( arrArg ) do -- Loop through Note Record indices from Rule argument doInsert("1 NOTE @N"..intNote.."@",intLine+intArg) -- Insert a link to each Note Record end return " Note Link" end -- local function doLinkNote local function doLinkSour(strInit,strRid,arrArg) -- Link any Record to associated Source Records; used by doRecordRule(), addLinkRecRule() -- strInit ~ Record Id initial letter -- strRid ~ Record Id numerical digits -- arrArg ~ List of Source Record Id local strPref = "1 SOUR @S" local intLine = tblRecord.Line if isRecord("INDI, FAM") then -- For Individual/Family insert Source link with others or end of record for intNext = intLine+1, #tblRecord do local intLev, strTag = getParts(tblRecord[intNext]) if intLev == 1 and ( strTag == "SOUR" or strTag == "CHAN" ) then intLine = intNext -1 break end end elseif isRecord("SOUR, REPO, OBJE, _PLAC") then -- For Source/Repository/Object/Place insert Note to link Source local strNeed = "1 NOTE" if strInit == "O" then strNeed = "1 _NOTE" end -- Object record note tag local intNext, strTag = findLongText(intLine,1,strNeed,"1 CHAN") if strTag == strNeed then -- Local NOTE already exists repeat intNext = intNext + 1 -- Find end of local NOTE local intLev, strTag = getParts(tblRecord[intNext] or "1 CHAN") until intLev < 2 or not dicAnyCont[strTag] strNeed = "2 CONT" -- NOTE already exists so use CONTinuation end local strNote = " Named List:\tSee linked Source" if #arrArg > 1 then strNote = strNote.."s" end doInsert(strNeed..strNote,intNext) -- Source/Repository/Object/Place records need a Note to link to Source Record intLine = intNext strPref = "2 SOUR @S" end for intArg, intSour in ipairs ( arrArg ) do -- Loop through Source Record indices from Rule argument doInsert(strPref..intSour.."@",intLine+intArg) -- Insert a link to each Source Record end return " Source Link" end -- local function doLinkSour local function doShareFact(strInit,strRid,arrArg) -- Insert shared Witness Role Fact; used by doRecordRule(), addLinkRecRule() -- V2.0 -- strInit ~ Record Id initial letter -- strRid ~ Record Id numerical digits -- arrArg ~ List of Witness Fact Id local intChan = tblRecord.Line + 1 local strLine = tblRecord[intChan] while strLine:upper() ~= "1 CHAN" do -- Find CHANge line number (allowing for dummy "1 chan") -- V2.2 intChan = intChan + 1 strLine = tblRecord[intChan] end for intArg, intFact in ipairs ( arrArg ) do -- Loop through Witness Fact indices from Rule argument local tblFact = arrFact[intFact] for intLine, strLine in ipairs (tblFact.Line) do -- Insert Witness Fact lines & numbers just before CHANge line doInsert(strLine,intChan,tblFact.Numb[intLine]) -- V4.0 intChan = intChan + 1 end end return " Fact Role" end -- local function doShareFact local function doRecordRule(strInit,strRid) -- Perform whole Record action to link Note/Source Records & Witness Facts; used by addLinkRecRule() -- strInit ~ Record Id initial letter -- strRid ~ Record Id numerical digits local arrItem = {} -- List of items added by functions for strArg, arrArg in pairs ( tblRecord.Arg ) do -- Loop through Rule argument functions table.insert( arrItem, strArg(strInit,strRid,arrArg) ) -- Invoke function with list of argument id end if strInit == "O" then setObjectUsed(strRid) -- Use the Sid=0 & Rid key to set Media Object details as Used -- V3.3 end local strRule = "0 @"..strInit..strRid.."@" -- # dicRule[strRule].Item = "Add"..table.concat(arrItem,",") -- Customise depending on items added -- moved to penultimate line of this function -- V4.6 doDropRule( {strRule} ) -- Remove the expired dictionary rule local dicRule = dicRule[" @"..strInit.."@"] if dicRule and type( dicRule.New ) == "function" then -- Detect & use any generic dictionary rule local intLine = tblRecord.Line -- Usually 1, except for copied File Root INDI record after HEADer -- V3.7 local strLine = tblRecord[intLine] if strLine:match(strRule) then -- Ensure chosen record header line will match generic rule -- V3.7 tblRecord.Arg = dicRule.Arg -- Add generic rule Argument to Record table tblRecord.Title= dicRule.Title -- Add generic Title & Item to Record table -- V3.3 tblRecord.Item = dicRule.Item return strLine:gsub(dicRule.Old,dicRule.New) -- Use the original old rule on the original record header -- V3.3 -- V3.7 --# return dicRule.New(strInit,strRid) -- Does not work unless captures happen to match old rule. else fhMessageBox("\n Record mismatches generic rule. \n") -- Report unexpected record versus rule mismatch -- V3.7 return nil -- Otherwise retain original record header end end tblRecord.Item = "Add"..table.concat(arrItem,",") -- Customise depending on items added -- V4.6 return nil -- Otherwise retain original record header end -- local function doRecordRule() local function addLinkRecRule(strRule,strType,doFunc,intArg) -- Create record tag Rule for associated Record needing a Note/Source/Fact; used by doListIds(), doFileRoot(), doSaveFacts() -- strRule ~ Lev & Id pattern for Rule.Old parameter -- strType ~ Record type tag for Rule.Old parameter -- doFunc ~ Function needed to handle Rule.Arg Id -- intArg ~ Number to add to Rule.Arg local strKey = strRule:gsub("[%(%)]","") -- Remove parentheses from rule pattern if not dicRule[strKey] then -- Define new Rule parameters local newRule = { Rule=#arrRule+1; Title="Record Header"; Old="^"..strRule..strType..".*"; New=doRecordRule; Arg={}; } dicRule[strKey] = newRule end if not dicRule[strKey].Arg[doFunc] then -- Ensure function associated argument array exists dicRule[strKey].Arg[doFunc] = {} end table.insert(dicRule[strKey].Arg[doFunc],intArg) -- Add the Note/Source/Fact Id to the Rule argument end -- local function addLinkRecRule local function doListName(strName) -- Convert a Named List to Note/Source Record -- strName ~ Name of list tblRecord.List = tblRecord.Arg local isNote = ( tblRecord.List == "NOTE" ) if isNote then intNote = intNote + 1 -- Use next available Note Record Id for new Note Record header -- V1.8 local strNote = strPrefix.."Named List:\t"..strName -- V3.3 dicName["N"..intNote] = strNote doInsert("0 @N"..intNote.."@ NOTE "..strNote) tblRecord.Pref = "1 CONT" -- Note Record text prefix -- V4.0 else intSour = intSour + 1 -- Use next available Source Record Id local strTitle = strPrefix.."Named List: "..strName -- V3.3 dicName["S"..intSour] = strTitle doInsert("0 @S"..intSour.."@ SOUR") -- New Source Record header & title -- V1.8 doInsert("1 TITL "..strTitle) tblRecord.Pref = "1 TEXT" -- Source Record text prefix -- V4.0 end return "" -- Delete original line end -- local function doListName local function doListFlag(strVal) -- Convert a Named List Flag -- V1.8 -- strVal ~ Value of list flag local isNote = ( tblRecord.List == "NOTE" ) if isNote then doInsert("1 CONT Flag:\tEditing Enabled") -- Make List Flag a Note Record Continuation line else doInsert("1 PUBL Editing Enabled") -- Make List Flag a Source Record Publication Info line -- V1.8 end return "" -- Delete original line end -- local function doListFlag local function doListNote(strLev,strVal) -- Convert a Named List local Note -- strLev ~ Level of list note -- strVal ~ Value of note text local isNote = ( tblRecord.List == "NOTE" ) if isNote then doInsert("1 CONT Note:\t"..strVal) -- Make List Note a Note Record Continuation line strLev = "1 " else if strLev == "2" then doInsert("1 NOTE "..strVal) -- Make List Note a Source Record local Note -- V1.8 else doInsert("2 CONT "..strVal) -- Make List Note a Source Record Text From Source CONTinuation -- V1.8 end strLev = "2 " end local intNext = tblRecord.Line+1 while intNext <= #tblRecord do -- Search subsequent lines for CONC/CONT -- V1.8 local intNxt, strTag, strVal = getParts(tblRecord[intNext]) local strGap = " " if not strTag:match("^CON[CT]") then break end if isNote and strTag == "CONT" then strGap = " \t" end -- Indent Note Record CONTinuation lines with tab -- V1.8 doInsert(strLev..strTag..strGap..strVal) -- Insert Note CONCatenation/CONTinuation line doDelete(intNext) end return "" -- Delete original line end -- local function doListNote local function doListIds(strRids) -- Convert a Named List list of record Idents -- strRids ~ List of record ids local isNote = ( tblRecord.List == "NOTE" ) local strPre = tblRecord.Pref -- Set prefix to 1 CONT for Note, or 1 TEXT or 2 CONT for Source -- V4.0 local tblRids = strRids:split(",") for intRid, strRid in ipairs (tblRids) do -- Compose all the Record Id elements local strRule, strType = getElements(strRid) doInsert(strPre..strType:gsub("^ _"," ")..":\t"..getRidName("@"..strRid.."@")) strPre = strPre:gsub("^1 TEXT","2 CONT") if dicPlac[strRid] then strRid = dicPlac[strRid] end -- Convert Place Id to Source Id as required if isNote and strRid:match("^[IFSRO]") then -- Link to Note Record not allowed in NOTE/SUBM/SUBN/_PLAC records addLinkRecRule(strRule,strType,doLinkNote,intNote) -- Create record tag Rule for associated Record elseif not isNote and strRid:match("^[IFNSRO]") then -- Link to Source Record not allowed in SUBM/SUBN/_PLAC records addLinkRecRule(strRule,strType,doLinkSour,intSour) -- Create record tag Rule for associated Record end end tblRecord.Pref = strPre -- V4.0 return "" -- Delete original line end -- local function doListIds local function doKeepIds() -- Make Place Ids into Source Ids when Named List retained -- FH5 local intLine = tblRecord.Line for intNext = intLine+1, #tblRecord do -- Search for subsequent List Ids local strNext = tblRecord[intNext] if strNext:match("^2 _IDS (.*)") then strNext = strNext:gsub("(P%d+)",dicPlac) -- Substitute Place Ids with Source Ids doModify(strNext,intNext) end if strNext:match("^1 ") then break end end doDropRule({"2 _FLAG";"2 _IDS";}) -- Keep all other Named List tags -- V1.8 if dicRule["2 _NOTE"] then dicRule["2 _NOTE"].New = "2 _NOTE %1" end if dicRule["3 _NOTE"] then dicRule["3 _NOTE"].New = "3 _NOTE %1" end return nil -- Retain original line end -- local function doKeepIds local function doFileRoot(strRid) -- Convert the File Root to a Note/Source Record -- strRid ~ Record Id of File Root Individual local strRule, strType = getElements(strRid) tblRecord.List = tblRecord.Arg local isNote = ( tblRecord.List == "NOTE" ) -- Is a Note or Source required? if isNote then intNote = intNote + 1 -- Use next available Note Record Id addLinkRecRule(strRule,strType,doLinkNote,intNote) -- Create record tag Rule for associated Individual Record local strNote = strPrefix.."File Root:\t_ROOT" -- V1.8 -- V3.3 dicName["N"..intNote] = strNote doInsert("0 @N"..intNote.."@ NOTE "..strNote) -- V1.8 doInsert("1 CONT INDI:\t"..getRidName("@"..strRid.."@")) -- V1.9 else intSour = intSour + 1 -- Use next available Source Record Id -- V1.8 addLinkRecRule(strRule,strType,doLinkSour,intSour) -- Create record tag Rule for associated Individual Record local strTitle = strPrefix.."File Root: _ROOT" -- V1.8 -- V3.3 dicName["S"..intSour] = strTitle doInsert("0 @S"..intSour.."@ SOUR") -- New Source Record header & title -- V1.8 doInsert("1 TITL "..strTitle) doInsert("1 TEXT INDI:\t"..getRidName("@"..strRid.."@")) -- V1.9 end return "" -- Delete original line end -- local function doFileRoot local arrConflict = {} -- Array of Conflicting Place Record Names local isConflicts = false -- Is a Result Set of conflicts required ? local function doAnalyse(arrRecord,arrLineNo) -- Analyse record Names/Titles, Media _ASID, File _ROOT, Witnessed Facts, etc -- arrRecord ~ Array of text lines in GEDCOM record -- arrLineNo ~ Array of their import line numbers tblRecord = arrRecord -- Array of text lines in GEDCOM record tblLineNo = arrLineNo -- Array of their import line numbers tblRecord.Line = 1 -- Add line index to Record table local function doSaveFacts(tblRecord,arrLineNo,strRid) -- Make array of Witnessed Facts and create Rules to copy to Witness Individuals -- V2.0 -- tblRecord ~ Array of text lines in GEDCOM record -- arrLineNo ~ Array of their import line numbers -- strRid ~ Record Id of Principal Individual/Family if IntFhVersion == 5 or not dicRule["2 _SHAR"].Arg then return end -- Except if the Witnessed Facts are being kept or removed -- V3.8 local is_SHAR = false local arrLine = {} local arrNumb = {} doConJoin(tblRecord,arrLineNo) for intLine = 2, #tblRecord do -- Loop through entire record local strLine = doPrune(tblRecord[intLine]) local intLev, strTag, strVal = getParts(strLine) if intLev == 1 then is_SHAR = false -- Possible start of next Fact arrLine = {} arrNumb = {} elseif strTag == "_SHAR" then -- Shared/Witnessed Fact found if not is_SHAR then is_SHAR = true local _, strTag1, strVal1 = getParts(arrLine[1]) local _, strTag2, strVal2 = getParts(arrLine[2]) -- V3.3 local dicTag = doNameTag(strTag1) -- Synthesise Witness Fact (EVENt/_ATTRibute) -- V2.3 cater for UDF tag arrLine[1] = strFact..strVal1 -- V4.0 was "1 _ATTR "..strVal1 if dicTag.E or strTag1 == "RESI" then -- V4.0 "RESI" arrLine[1] = "1 EVEN" -- V2.8 to avoid Date Range problem in 5.5.1 end if strTag1 == "EVEN" or strFact:match(strTag1) then -- Cope with TYPE of existing EVENt/FACT/_ATTRibute -- V4.5 arrLine[2] = "2 TYPE "..strPrefix..strVal2.." Role" -- V3.3 elseif strTag2 == "TYPE" then -- Cope with TYPE Descriptor possibly on EMIG/IMMI -- V3.3 local strType = dicTag.Name:gsub("^.- (.+):%s+$","2 TYPE "..strPrefix..strVal2.." %1 Role") -- V3.3 arrLine[2] = strType else -- Else add TYPE for standard Event/Attribute local strType = dicTag.Name:gsub("^.- (.+):%s+$","2 TYPE "..strPrefix.."%1 Role") -- V3.3 table.insert(arrLine,2,strType) table.insert(arrNumb,2,arrNumb[1]) end table.insert(arrLine,"2 _SHAR @"..strRid.."@") -- Synthesise Principal Role table.insert(arrLine,"3 ROLE Principal") table.insert(arrNumb,arrLineNo[1]) table.insert(arrNumb,arrLineNo[1]) table.insert(arrFact,{Line=arrLine;Numb=arrNumb;}) -- Save this Fact end local strRule, strType = getElements(strVal:gsub("@","")) addLinkRecRule(strRule,strType,doShareFact,#arrFact) -- Create record tag Rule for Witness Individual Record end if not (strLine:match("^2 HUSB") or strLine:match("^2 WIFE") or strLine:match("^3 AGE")) then -- V3.3 table.insert(arrLine,strLine) -- Save the Fact Lines and Line Nos except Family fact Ages table.insert(arrNumb,arrLineNo[intLine]) end end end -- local function doSaveFacts local function doFlagObjeUsed(tblRecord) -- Flag Object Record Used -- V3.3 -- tblRecord ~ List of record lines if not isWipeLmo then for intLine = 2, #tblRecord do -- Process every OBJE link found -- V3.3 local strLine = tblRecord[intLine] local intLev, strTag, strVal, isLink = getParts(strLine) if strTag == "OBJE" and isLink then -- Synthesise dicObje[strKey] entry in FH V7 -- V4.0 local strSid, strRid = doMediaSeqId(intLine+1,intLev,strVal) if isCaption or isAllType or strSid == "0" then -- Unless PART~ABS or ALL~ABS with an _ASID/_SEQ/_AREA/_EXCL/NOTE -- V4.0 setObjectUsed(strRid) -- Use the Sid=0 & Rid key to set Media Object details as Used -- V3.3 end end end end end -- local function doFlagObjeUsed local strFam = "" -- Family record name buffer -- V4.0 moved inside doAnalyse() local dicType = -- Actions per record type dictionary for run-time efficiency (in Gedcom file order), only for doAnalyse() { HEAD = { { "^1 _ROOT @(I%d+)@$"; }; -- Associated File Root record Id -- V1.8 -- V4.0 "%1"; ""; }; INDI = { { "^1 NAME ?(.* /(.*)/.*)$"; }; -- Associated Individual record Name(NAME) -- V4.0 -- V4.0 function(strName,strSurname) return strName:convert("/.*/",strSurname:upper()) -- With uppercase Surname end; "[unnamed person]"; }; FAM = { { "^1 ([HW][UI][SF][BE]) @(.+)@"; "^1 (CHAN)(.*)"; }; -- Associated Family record Spouse Names(HUSB/WIFE/CHAN) -- V4.0 function(strTag,strRid) if strTag == "CHAN" then return strFam:gsub("^ and ","...of ") -- CHANge date/time reached so replace leading " and " with "...of " end strFam = strFam.." and "..strRecordName(strRid) -- Append HUSBand and WIFE names until CHANge date/time reached -- V2.8 strRecordName() return "" end; "...of (parents not known)"; }; OBJE = { { "^1 _?FILE (.*)"; "^%d TITL (.*)"; }; -- Associated Multimedia record Title(TITL)/File(_FILE) -- V4.0 "%1"; ""; }; NOTE = { { "^0 @N(%d+)@ NOTE(.*)"; }; -- Associated Note record first line of Text(NOTE) -- V4.0 function(strRid,strNote) intNote = math.max(intNote,tonumber(strRid)) -- Save largest Note Record Id for synthetic Note Records (Named Lists & File Root) dicMaxId.N = intNote return strNote:match("^ (.+)") or "" end; ""; }; REPO = { { "^1 NAME (.*)"; }; -- Associated Repository record Name(NAME) -- V4.0 "%1"; ""; }; SOUR = { { "^1 ABBR (.*)"; "^1 TITL (.*)"; }; -- Associated Source record Title(TITL)/Short Title(ABBR) -- V4.0 "%1"; ""; }; SUBM = { { "^1 NAME (.*)"; }; -- Associated Submitter record Name(NAME) -- V4.0 "%1"; "Unidentified submitter"; }; SUBN = { { "^1 FAMF (.*)"; }; -- Associated Submission record Family(FAMF) -- V4.0 "%1"; "Submission record"; }; _PLAC = { { "^0 @P%d+@ _PLAC (.*)"; }; -- Associated Place record first line Place(_PLAC) -- V1.6 -- V4.0 "%1"; ""; }; _RNOT = { { "^1 TEXT (.*)"; }; -- Associated Research Note record Text(TEXT) -- V4.0 "%1"; ""; }; _SRCT = { { "^1 NAME (.*)"; }; -- Associated Source Template record Name(NAME) -- V4.0 "%1"; ""; }; } local intLev, strRid, strType = getParts(tblRecord[1]) if strRid == "HEAD" then -- Special action for HEAD record dicName["H0"] = "HEAD" -- V2.2 strRid = "RootId" -- Special case for HEAD record to find File _ROOT record id -- V1.8 strType = "HEAD" else strRid = strRid:match("^@(%u%d+)@$") -- Rec Id for Name dictionary and Type for actions -- V1.8 strType = strType:match("_?%u+") end if strType and dicType[strType] then local arrSource = dicType[strType][1] -- Set record type search patterns with captures -- V4.0 local anyTarget = dicType[strType][2] -- Set record type replacement string/function dicName[strRid] = dicType[strType][3] -- Set record type name for missing Name/Title strFam = "" for intSource, strSource in ipairs ( arrSource ) do -- Search patterns in sequence -- V4.0 for intLine = 1, #tblRecord do local strName, intSub = tblRecord[intLine]:gsub(strSource,anyTarget) if #strName > 0 and intSub == 1 then dicName[strRid] = doPrune(strName) -- Dictionary entry for Record Name/Title if intSource >= #arrSource then break end -- Escape when patterns exhausted end end end dicName.Count = ( dicName.Count or 0 ) + 1 -- Count records -- V1.8 if strType == "INDI" then if strRid == dicName.RootId then -- Ensure File Root Individual record is after HEAD record -- V1.8 if dicName.Count <= 2 or not dicRule["1 _ROOT"] then -- V3.1 dicName.RootId = nil -- Clear RootId as Individual record is already after HEAD -- V1.8 or for Keep Custom Tag _ROOT -- V3.1 else doConJoin(tblRecord,arrLineNo) -- V4.0 dicName.RootRec = tblRecord -- Save RootRec as Individual record to insert after HEAD -- V1.8 dicName.RootLin = arrLineNo end end doSaveFacts(tblRecord,arrLineNo,strRid) -- Make dictionary entry for each Witnessed Individual Fact -- V2.0 elseif strType == "FAM" then doSaveFacts(tblRecord,arrLineNo,strRid) -- Make dictionary entry for each Witnessed Family Fact -- V2.0 elseif strType == "OBJE" then intObje = math.max(intObje,tonumber(strRid:sub(2))) -- Save largest Media Record Id for synthetic Media Records -- V1.8 dicMaxId.M = intObje dicMaxId.O = intObje doSaveMseq(tblRecord,strRid) -- Make dictionary entry for each Media _ASID/_SEQ -- V1.8 elseif strType == "NOTE" then local dicRule = dicRule[" @N@"] -- Legacy Note record tab related Record Id -- V2.6 if dicRule and dicRule.Arg and type(dicRule.Arg) == "table" then local strTab = strRecordName(strRid):match("^%((%u%l-)%):") -- V2.8 strRecordName() local strNid = dicRule.Arg[strTab] or "NI" if strNid then dicNote[strNid] = dicNote[strNid] + 1 strNid = strNid..dicNote[strNid] dicNote[strRid] = strNid end end elseif strType == "SOUR" then intSour = math.max(intSour,tonumber(strRid:sub(2))) -- Save largest Source Record Id for synthetic Source Records (Source Note) dicMaxId.S = intSour doFlagObjeUsed(tblRecord) -- Flag Object Record Used -- V3.3 if not isWipeAll and dicRule[" SOUR"] and dicRule[" SOUR"].Arg == "Copy Media" then -- V2.0 local intLink = 0 -- V4.6 local intObje = 0 for intLine = 2, #tblRecord do -- For FTL/FTM save every OBJE structure for doCitation() to reproduce local strLine = tblRecord[intLine] local intLev, strTag, strVal = getParts(strLine) if strTag == "OBJE" then intObje = intLev + 1 -- Detect start of OBJE structure elseif intLev < intObje then intObje = 0 end -- Detect end of OBJE structure if intObje > 0 then if not dicSour[strRid] then dicSour[strRid] = {} end table.insert(dicSour[strRid],strLine) -- Save each OBJE structure tag line end -- URL can start with ftp://, http://, or https:// -- URL may contain A-Z a-z 0-9 - . _ ~ : / ? # @ ! $ & ' ( ) * + , ; = % local strURL = strVal:match("[hf]t?tps?://[A-Za-z0-9%-%._~:/%?#@!%$&'\\%(%)%*%+,;=%%]+") if strURL and intLink < 1 then -- Detect first URL link -- V4.6 if not dicSour[strRid] then dicSour[strRid] = {} end table.insert(dicSour[strRid],"1 _LINK "..strURL) -- Save first URL link -- V4.6 intLink = 1 end end end elseif strType == "_PLAC" then -- V1.6 local _, intComma = tblRecord[1]:gsub(",",",") -- Determine maximum comma separated Place parts -- V3.8 intParts = math.max(intParts,intComma + 1) doFlagObjeUsed(tblRecord) -- Flag Object Record Used -- V3.3 if dicRule[" @P@"] then -- Special rule for Place records local strArg = dicRule[" @P@"].Arg or "" -- V3.8 if #strArg == 0 then if dicRule[" @P@"].Item:match("5.5.1") then -- Save MAP, LATI, LONG structure for doPlaceTag() to reproduce in Place fields -- V3.8 local dicMap = {} for intLine = 2, #tblRecord do local strLine = tblRecord[intLine] local _, strTag = getParts(strLine) if strTag == "MAP" or strTag == "LATI" or strTag == "LONG" then table.insert(dicMap,strLine) end end dicPlac[strRecordName(strRid)] = dicMap end elseif strArg:match("SOUR") then -- Synthetic Source record Place names never tidied -- V3.3 intSour = intSour + 1 -- Can use intSour here as Sources are before Places in Gedcom file dicPlac[strRid] = "S"..intSour -- Make dictionary entry for Place Id to Source Id dicCite[strRecordName(strRid)] = dicPlac[strRid] -- Make dictionary entry for Place Name to Source Id for doPlaceTag() -- V1.8 -- V2.8 strRecordName() elseif isTidy and #strArg > 0 then -- Is tidying enabled and record kept? -- V3.3 local oldPlac = strRecordName(strRid) local newPlac = strTidyText(oldPlac) -- Tidy the Place name -- V3.3 local badPlac = dicTidy[newPlac.."\n"] if badPlac then -- Report a Place name conflict and undo tidying local badRid for oldRid, strPlac in pairs (dicName) do -- Obtain other Place name Record Id if strPlac == newPlac and oldRid ~= strRid then badRid = oldRid break end end local notPlac = oldPlac local notRid = strRid if oldPlac == newPlac then -- Current Place name has not needed tidying notPlac = badPlac notRid = badRid dicTidy[badPlac] = badPlac -- So undo the tidying of other Place name dicTidy[badPlac.."\n"] = badPlac dicName[badRid] = badPlac -- And revert its Rid entry name end local strRid = strRid:sub(2) local badRid = badRid:sub(2) -- Log each Record Id local notRid = notRid:sub(2) table.insert(arrConflict,{ tonumber(strRid); tonumber(badRid); }) if not isConflicts then if 1 == iup_gui.WarnDialogue(" Conflicting Place Record Names ","\n"..oldPlac.." ["..strRid.."]\n and \n"..badPlac.." ["..badRid.."]\n both tidy to \n"..newPlac.." \n so \n"..notPlac.." ["..notRid.."]\n has NOT been Place name tidied \n\n The 'Result Set' button will list all Conflicting Place Record Names \n"," Result Set "," Acknowledge ") then isConflicts = true -- Inhibit further warnings and terminate Plugin with Result Set end end newPlac = oldPlac -- Otherwise, undo current Place name tidying end dicTidy[oldPlac] = newPlac dicTidy[newPlac.."\n"] = oldPlac -- Populate the Place name tidying dictionary used for all subsequent tidying if newPlac ~= oldPlac then dicName[strRid] = newPlac -- Correct the Rid entry name -- V3.3 end end end elseif strType == "SUBM" then -- Submitter -- V3.3 doFlagObjeUsed(tblRecord) -- Flag Object Record Used -- V3.3 end end return isConflicts, arrConflict end -- local function doAnalyse local function doFactDefs() -- Add Fact Definitions after Header if not isFact then return "" end -- If disabled then just remove original line -- V3.3 local tblFact = {} -- Fact table local dicLine = { Name="1 _NAME "; Label="1 _LABEL "; Abbr="1 _ABBR "; Template="1 _SENT "; Role="1 _ROLE "; Verb="2 _VERB "; Sentence="2 _SENT "; } local function doLoadFactsTable(strFactsFile) -- Import Fact Set file to Fact table local intFact = 0 -- Fact table ordinal index per Fact local arrPrev = {} -- Previous entry in case of Hidden Fact for strLine in encoder.FileLines(strFactsFile,"UTF-8") do -- Read each line from Fact Set file -- V3.6 local strFact = strLine:match("^%[FCT%-(.*)%]$") -- "[FCT-tag-type]" or "[FCT-tag-type-ROLE]" where type is IE|IA|FE|FA for Ind/Fam & Event/Attr if strFact then strFact, intRole = strFact:gsub("%-ROLE$","") -- Setup Fact table in Fact order tblFact[strFact] = tblFact[strFact] or (#tblFact + 1) -- Point FCT tag-type at ordinal entry intFact = tblFact[strFact] if intRole == 0 then arrPrev = tblFact[intFact] or {Hidden=true} -- Preserve previous entry in case of Hidden Fact tblFact[intFact] = {} -- Create empty ordinal entry --# tblFact[intFact][1] = "0 _FACT "..strFact:gsub("^_ATTR","FACT"):gsub("^([FACTEVN]+)%-.-%-([IF][AE])$","%1-%2") -- 24 Nov 2016 for 5.5.1 to match Root array below tblFact[intFact][1] = "0 _FACT "..strFact:gsub("^([_ATREVN]+)%-.-%-([IF][AE])$","%1-%2") -- 28 Nov 2016 for 5.5 to match Root array below < this is current end else if strLine == "Hidden=Y" then -- Discard Hidden Fact if arrPrev.Hidden then tblFact[intFact].Hidden = true -- No, previous entry was Hidden else tblFact[intFact] = arrPrev -- Yes, revert to previous entry end else strLine = strLine:gsub("^Role%d+%-","") -- Remove leading "Role9-" local strTag = strLine:gsub("^(.-)%d-=",dicLine) -- Convert Line to Tag if strTag:match("^[12] _") then table.insert(tblFact[intFact],strTag) -- Load Tag into Fact table end end end end end -- local function doLoadFactsTable -- Use the GroupIndex to process Fact Sets in reverse order to obscure Facts (like Change Any Fact Tag) local dicGroupIndex = {} local strDataFolder = fhGetContextInfo("CI_PROJECT_DATA_FOLDER").."\\Fact Types" local strGroupIndex = strDataFolder.."\\GroupIndex.fhdata" if general.FlgFileExists(strGroupIndex) then for strLine in encoder.FileLines(strGroupIndex,"ANSI") do -- Read each line in Project only GroupIndex -- V4.2 local strFactSet, intFactSet = strLine:match("^(.+)=(%d+)$") -- Extract Fact Set name and ordinal position intFactSet = tonumber(intFactSet) if strFactSet and intFactSet and not dicGroupIndex[intFactSet] then -- Compose Fact Set filename local strFactsFile = strDataFolder.."\\"..strFactSet..".fhf" if general.FlgFileExists(strFactsFile) then -- Fact Set name and file exist dicGroupIndex[intFactSet] = strFactsFile -- Save filename in ordinal order end end end end local intGroupIndex = #dicGroupIndex local strDataFolder = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Fact Types" local strGroupIndex = strDataFolder.."\\Standard\\GroupIndex.fhdata" if general.FlgFileExists(strGroupIndex) then for strLine in encoder.FileLines(strGroupIndex,"ANSI") do -- Read each line in ProgramData GroupIndex -- V3.6 local strFactSet, intFactSet = strLine:match("^(.+)=(%d+)$") -- Extract Fact Set name and ordinal position intFactSet = tonumber(intFactSet) if strFactSet and intFactSet and not dicGroupIndex[intFactSet+intGroupIndex] then -- Compose Fact Set filename local strFactsFile = strDataFolder.."\\Custom\\"..strFactSet..".fhf" if strFactSet == "Standard" then strFactsFile = strDataFolder.."\\Standard\\Standard.fhf" end if general.FlgFileExists(strFactsFile) then -- Fact Set name and file exist dicGroupIndex[intFactSet+intGroupIndex] = strFactsFile -- Save filename in ordinal order end end end end for intFactSet = #dicGroupIndex, 1, -1 do -- Reverse order of Fact Sets doLoadFactsTable(dicGroupIndex[intFactSet]) -- Import Fact Set file to Fact table end for intFact, arrFact in ipairs (tblFact) do for intFact, strLine in ipairs (arrFact) do -- Export Fact Tags from Fact table doInsert(strLine) end end return "" -- Delete original line end -- local function doFactDefs local strTPTmissing = "IBASM;IEMIG;INCHI;INMR;IBAPL;ICONL;IENDL;ISLGC;FANUL;FCENS;FDIVF;FENGA;FMARB;FMARC;FMARL;FMARS;FSLGS;" local function doMultiFact(strInit) -- Convert multiple fact instances, etc, for TPT -- V3.2 -- strInit ~ Initial I/F for current record local dicFact = {} local intLine = tblRecord.Line + 1 while intLine <= #tblRecord do local strLine = tblRecord[intLine] local intLev, strTag, strVal = getParts(strLine) if intLev == 0 or strLine == "1 chan" then break end local dicTag = doNameTag(strTag) if intLev == 1 and dicTag[strInit] == 0 and strTag ~= "NAME" -- Level 1 INDI/FAM Fact, etc, but exclude NAME for now and not tblRecord[intLine+1]:match("^2 TYPE ") then -- Exclude ASSO/_ATTR/EVEN with TYPE descriptor local strName = dicTag.Name:match("^#.( .+):") -- V2.3 cater for UDF tag local intFact = dicFact[strTag] or 0 dicFact[strTag] = intFact + 1 if intFact > 0 -- Convert multi-instance facts or strTPTmissing:find(strInit..strTag..";") then -- and missing TPT facts for now doModify(strFact.." "..strVal,intLine) intLine = intLine + 1 if intFact == 0 then intFact = "" end doInsert("2 TYPE"..strName..intFact,intLine) -- Add strPrefix ? end end intLine = intLine + 1 end return nil -- Retain original line end -- local function doMultiFact local function doCens2Resi() -- Convert Individual CENSus to RESIdence for Ancestry Census hints -- V3.4 if isRecord("INDI") then return "1 RESI" end return nil -- Retain original line end -- local function doCens2Resi local function addRule(arrRoot) local dicRule = { Rule=#arrRule+1; Title=arrRoot[1]; Item=arrRoot[2]; Row=arrRoot[3]; Old=arrRoot[4]; New=arrRoot[5]; Arg=arrRoot[6]; Mode=" "..arrRoot[7].." "; Used=0; } -- V2.0 Row --# if dicRule.Mode:match("%u[id%u][nr%u%d]") then dicRule.Item = dicRule.Item.." ("..dicRule.Mode:gsub(" +"," ")..")" end -- V2.8 if dicRule.Mode:match("%w") then dicRule.Item = dicRule.Item.." ("..dicRule.Mode:gsub("%l%l%w",""):gsub(" +"," ")..")" end -- V3.3 table.insert(arrRule,dicRule) -- Add program abbreviation to droplist item (except lower case) then save rule end -- local function addRule -- GEDCOM tag conversion rules local dicHeadSour = { FH5="FAMILY_HISTORIAN"; FH6="FAMILY_HISTORIAN"; FMP="FINDMYPAST"; FTL="FTM"; FTM="FTM"; RMT="ROOTSMAGIC"; } -- V4.0 FTL/FTM -- V4.1 FH5, FH6, GST -- GUI Label Title ; GUI DropList Action ; Row ; Old Tag ; New Tag ; Action Argument ; GEDCOM Mode -- Notes addRule({"Header Record" ;"Move File Root Record" ; "p" ;"^0 HEAD" ;doHeaderRec ;nil ;"Std FTL-" }) -- V1.8 -- V3.3 FTL- addRule({"Header Record" ;"Move File Root Record" ; "q" ;"^0 HEAD" ;doHeaderRec ;"Header Rec: HEAD" ;"FTL+" }) -- V2.0 -- V3.3 FTL+ addRule({"Product Title" ;"Change to Target" ; "p" ;"^1 SOUR .*" ;doHeadSour ;dicHeadSour ;"Std gst " }) -- V1.8 FTL -- V2.4 RMT -- V4.0 -- V4.3 gst addRule({"Product Version" ;"Family Historian 5" ; "p" ;"^1 SOUR .*" ;doHeadVers ;"5.0.11" ;"FH5 " }) -- V4.0 addRule({"Product Version" ;"Family Historian 6" ; "p" ;"^1 SOUR .*" ;doHeadVers ;"6.2.7" ;"FH6 " }) -- V4.0 addRule({"Gedcom Name Tag" ;"Plugin Version" ; "p" ;"^2 NAME .*" ;doHeadName ;nil ;"Std fh5 fh6" }) -- V1.3 ~ previously was "Min TNG", except fh5 -- V4.0 and fh6 addRule({"Gedcom Filename" ;"Remove entirely" ; "p" ;"^1 FILE .*" ; "" ;nil ;"Std fh5 fh6" }) -- V1.3 ~ previously was "Min TNG", except fh5 -- V4.0 and fh6 addRule({"Gedcom Version" ;"Gedcom Release 5.5" ; "p" ;"^2 VERS (.*)" ;doVersion ;"5.5" ;"Std " }) -- V4.0 Set Gedcom Version explicitly addRule({"Gedcom Version" ;"Gedcom Release 5.5.1" ; "q" ;"^2 VERS (.*)" ;doVersion ;"5.5.1" ;"Str AGS FTM GFT GSP HER MFT RFT CEB " }) -- V2.8 Str Mdr GSP -- V3.8 AGS RFT CEB -- V4.0 FTM GFT GST HER MFT (not TNG as it needs EVEN not FACT) -- V4.3 not GST addRule({"Gedcom Version" ;"Gedcom Release Kept" ; "r" ;"^2 VERS (.*)" ;doVersion ;nil ;"GST " }) -- V4.3 GST addRule({"Gedcom Variant" ;"Remove entirely" ; "p" ;"^2 _VAR .*" ; "" ;nil ;"Std fh5 fh6" }) -- "2 _VAR DSR" in all FH exported GEDCOM, so except fh5 -- V4.0 and fh6 addRule({"Character Encode" ;"Choose Encoding" ; "p" ;"^1 CHAR .*" ;doCharCode ;nil ;"Std " }) addRule({"Character Encode" ;"Choose Encoding" ; "q" ;"^1 CHAR .*" ;doCharCode ;{"1 DEST ANY";"1 PLAC";"2 FORM ";} ;"AGS" }) -- V3.8 AGS mandatory tags addRule({"Receiving System" ;"Remove entirely" ; "p" ;"^1 DEST .*" ; "" ;nil ;"Std ags" }) -- V2.4 -- V3.8 ags addRule({"Gedcom Copyright" ;"Remove entirely" ; "p" ;"^1 COPR .*" ; "" ;nil ;"Std " }) -- V2.4 addRule({"Unique Identity" ;"Remove entirely" ; "p" ;"^1 _UID .*" ;doFactDefs ;nil ;"Std fh5 fh6" }) -- V3.3 All can export Fact Definitions if option enabled, except fh5 -- V4.0 and fh6 addRule({"Prepared Citation" ;"Remove entirely" ; "p" ;"^(1) _PCIT" ;doRemoveAll ;nil ;"Std " }) -- V4.0 Remove the Prepared Citation addRule({"Companion Used" ;"Remove entirely" ; "p" ;"^1 _USED .*" ; "" ;nil ;"Std " }) -- V3.1 addRule({"Header Submitter" ;"Remove entirely" ; "a" ;"^1 SUBM .*" ; "" ;nil ;"FTL GSP HER" }) -- V1.8 FTL -- V2.0 GSP HER LFT -- V4.0 LFT removed addRule({"Named List Entry" ;"Create Note Record" ; "A" ;"^1 _LIST (.*)" ;doListName ;"NOTE" ;"Std+" }) addRule({"Named List Entry" ;"Remove entirely" ; "D" ;"^(1) _LIST .*" ;doRemoveAll ;nil ;"Std- FTL FTA" }) -- V1.3 FTA -- V1.8 FTL -- V2.8 Mdr addRule({"Named List Entry" ;"Create Source Record" ; "B" ;"^1 _LIST (.*)" ;doListName ;"SOUR" ;"ANC+ FMP+ GSP+ HER+ LFT+ ZPG+" }) -- V1.8 FTL option -- V1.9 HER -- V2.0 LFT -- V2.2 ANC addRule({"Named List Entry" ;"Keep Custom Tag" ; "C" ;"^1 _LIST .*" ;doKeepIds ;nil ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 addRule({"Named List Flag" ;"Add to Note/Source" ; "p" ;"^2 _FLAG .*" ;doListFlag ;nil ;"Std fh5 fh6 gst" }) -- V1.8 -- V3.1 gst & fh5 -- V4.0 fh6 addRule({"Named List Note" ;"Add to Note/Source" ; "p" ;"^(2) _NOTE (.*)" ;doListNote ;nil ;"Std fh5 fh6 gst" }) -- V3.1 gst & fh5 -- V4.0 fh6 addRule({"Named List Note" ;"Add to Note/Source" ; "p" ;"^(3) _NOTE (.*)" ;doListNote ;nil ;"Std fh5 fh6 gst" }) -- V3.1 gst & fh5 -- V4.0 fh6 addRule({"Named List Idents" ;"Add to Note/Source" ; "p" ;"^2 _IDS (.*)" ;doListIds ;nil ;"Std fh5 fh6 gst" }) -- V3.1 gst & fh5 -- V4.0 fh6 addRule({"File Root" ;"Create Note Record" ; "A" ;"^1 _ROOT @(.*)@" ;doFileRoot ;"NOTE" ;"Std+" }) addRule({"File Root" ;"Remove entirely" ; "D" ;"^1 _ROOT .*" ; "" ;nil ;"Std- FTL" }) -- V1.4 returned to V1.2 -- V1.8 FTL -- V2.8 Mdr addRule({"File Root" ;"Create Source Record" ; "B" ;"^1 _ROOT @(.*)@" ;doFileRoot ;"SOUR" ;"ANC+ GSP+ HER+ LFT+ ZPG+" }) -- V1.8 FTL option -- V1.9 HER -- V2.0 LFT -- V2.2 ANC addRule({"File Root" ;"Keep Custom Tag" ; "C" ;"^1 _ROOT" ;"1 _ROOT" ;nil ;"FH5 FH6 FTA GST" }) -- V1.4 returned to V1.2 -- V3.1 GST -- V4.0 FH6 & removed FMP addRule({"Individual Name" ;"Tail to Record Note" ; "a" ;"^1 NAME (.*)" ;doIndiName ;"~Name Tail" ;" " }) -- V1.3 GFT -- V2.0 -- V4.0 GFT removed addRule({"Individual Name" ;"Note Name Instance" ; "p" ;"^1 NAME (.*)" ;doIndiName ;nil ;"Std RMT" }) -- V1.8 Std -- V2.0 -- V4.0 gft removed addRule({"Name Prefix" ;"Move to 1 TITL" ; "a" ;"^2 NPFX (.*)" ;doPromote ;"1 TITL " ;"FMP FTL FTM ZPG" }) -- V1.8 FTL/FTM -- V2.0 table removed addRule({"Name Suffix" ;"Append to NAME" ; "a" ;"^2 NSFX (.*)" ;doPutName ;nil ;" FTL FTM ZPG" }) -- V1.8 FTL/FTM addRule({"Name Suffix" ;"Move to Record Note" ; "p" ;"^2 NSFX (.*)" ;doRecNote ;"~Name Suffix" ;"FMP " }) -- V2.2 FMP -- V1.3 RMT ??? addRule({"Name Given" ;"Move to Record Note" ; "a" ;"^2 GIVN (.*)" ;doRecNote ;"~Name Given" ;"FMP GFT GSP HER MYH RMT" }) -- V1.8 FTL -- V1.3 GFT RMT -- V1.9 HER -- V2.0 GSP -- V3.2 MYH addRule({"Surname Prefix" ;"Move to Record Note" ; "a" ;"^2 SPFX (.*)" ;doRecNote ;"~Surname Prefix" ;"FMP GFT GSP HER AQP" }) -- V1.8 FTL -- V1.3 GFT -- V1.9 HER -- V2.0 GSP -- V3.8 AQP addRule({"Surname" ;"Move to Record Note" ; "a" ;"^2 SURN (.*)" ;doRecNote ;"~Surname" ;"FMP GFT GSP HER MYH RMT" }) -- V1.8 FTL -- V1.3 GFT RMT -- V1.9 HER -- V2.0 GSP -- V3.2 MYH addRule({"Person Nickname" ;"Keep Standard Tag" ; "A" ;"^2 NICK" ;"2 NICK" ;nil ;"Std " }) -- V1.8 Std addRule({"Person Nickname" ;"Move to 1 ALIA" ; "B" ;"^2 NICK (.*)" ;doPromote ;"1 ALIA " ;"FTL FTM" }) -- V1.8 FTL/FTM -- V2.0 table removed -- V2.2 "or" addRule({"Person Nickname" ;'Use " " in NAME' ; "C" ;"^2 NICK (.*)" ;doPromote ;"1 NAME " ;"GSP ZPG" }) -- V1.8 FTL -- V2.0 table removed GSP -- V4.0 FTL/FTM removed addRule({"Person Nickname" ;"Promote to 1 _NICK" ; "D" ;"^2 NICK (.*)" ;doPromote ;"1 _NICK " ;" " }) -- TNG ??? addRule({"Given Name" ;"Move to Record Note" ; "A" ;"^2 _USED (.*)" ;doRecNote ;"~Given Name" ;"Std+" }) addRule({"Given Name" ;"Remove entirely" ; "F" ;"^2 _USED .*" ; "" ;nil ;"Std-" }) -- V2.8 Mdr addRule({"Given Name" ;"Move to 1 ALIA" ; "B" ;"^2 _USED (.*)" ;doPromote ;"1 ALIA " ;"FTL FTM" }) -- V1.8 FTL/FTM -- V2.0 table removed addRule({"Given Name" ;'Use " " in NAME' ; "C" ;"^2 _USED (.*)" ;doPromote ;"1 NAME " ;"ZPG " }) -- V1.8 FTL -- V2.0 table removed ZPG -- V4.0 FTL/FTM removed addRule({"Given Name" ;"Append to 2 NICK" ; "G" ;"^(2) _USED (.*)" ;doPutOther ;"NICK,1" ;"MYH GST-" }) -- V1.3 GST alternative option -- V3.2 MYH addRule({"Given Name" ;"Promote to 1 _USED" ; "D" ;"^2 _USED (.*)" ;doPromote ;"1 _USED " ;"TNG " }) -- TNG see http://www.tng.lythgoes.net/wiki/index.php?title=Desktop_gotchas#Family_Historian_.28FH.29 addRule({"Given Name" ;"Keep Custom Tag" ; "E" ;"^2 _USED" ;"2 _USED" ;nil ;"FH5 FH6 GST+" }) -- V1.6 FH5 -- V1.3 GST -- V4.0 FH6 addRule({"Record Flags" ;"Make Custom Event" ; "A" ;"^1 _FLGS" ;doRecFlags ;"Record Flag" ;"Std+ GST-" }) -- V3.3 addRule({"Record Flags" ;"Remove entirely" ; "D" ;"^(1) _FLGS" ;doRemoveAll ;nil ;"Std- gst" }) -- V2.8 Mdr addRule({"Record Flags" ;"Move to Record Note" ; "B" ;"^1 _FLGS" ;doRecFlags ;"Record Flag" ;"FTL+" }) -- V3.3 FTL+ addRule({"Record Flags" ;"Keep Custom Tag" ; "C" ;"^1 _FLGS" ;"1 _FLGS" ;nil ;"FH5 FH6 GST+ TNG" }) -- V3.1 GST -- V4.0 FH6 -- Maybe "Std" Make Custom Event is better for TNG too, plus TNG _LIVING and _PRIVATE tags? addRule({"Marriage Status" ;"Move to Record Note" ; "A" ;"^1 _STAT (.*)" ;doRecNote ;"Marriage Status" ;"Std+" }) addRule({"Marriage Status" ;"Remove entirely" ; "C" ;"^1 _STAT .*" ; "" ;nil ;"Std-" }) -- V2.8 Mdr ZPG ??? addRule({"Marriage Status" ;"Keep Custom Tag" ; "B" ;"^1 _STAT" ;"1 _STAT" ;nil ;"FH5 FH6 GST LFT" }) -- V1.6 FH5 -- V3.1 GST -- V3.3 LFT -- V4.0 FH6 --? addRule({"Marriage Status" ;"Change to 1 _UST" ; "D" ;"^1 _STAT" ;"1 _UST" ;nil ;"HER " }) -- V1.9 HER? addRule({"Witness Role" ;"Move to Fact Note" ; "A" ;"^2 _SHA%u (.*)" ;doWitness ;"Witness Role" ;"Std+" }) -- V1.6 for FH V6.0 Rule index "2 _SHAR" and "2 _SHAN" addRule({"Witness Role" ;"Remove entirely" ; "C" ;"^(2) _SHA%u .*" ;doRemoveAll ;nil ;"Std- FTL" }) -- V1.6 for FH V6.0 erase _SHAR/N & ROLE, etc -- V2.0 FTL -- V2.8 Mdr ZPG ??? addRule({"Witness Role" ;"Keep Custom Tags" ; "B" ;"^2 _SHA(%u)" ;"2 _SHA%1" ;nil ;"FH6 FTA GST HER LFT MFT RMT TNG" }) -- V1.6 for FH V6.0 -- V2.0 FTA LFT RMT use "2 _SHAR" & "3 ROLE" -- V3.1 GST TNG -- V4.0 FH6 HER MFT addRule({"Witness Role" ;"Change to 2 ASSO" ; "D" ;"^2 _SHA(%u)" ;"2 ASSO" ;nil ;" " }) -- V1.9 HER -- V4.0 HER removed addRule({"Witness Role" ;"Change to 2 _WITN" ; "E" ;"^2 _SHA(%u)" ;"2 _WITN" ;nil ;"GFT " }) -- V4.0 GFT --! LFT just repeats a fact if Principal is also a Witness, so could use LFT name only structure: 2 _SHAR 3 GIVN given 3 SURN surname 3 ROLE role addRule({"Witness Role" ;"Change to 3 TITL" ; "p" ;"^3 ROLE" ;"3 TITL" ;nil ;" " }) -- V1.9 HER Should only apply when previous line is 2 ASSO -- V4.0 HER removed addRule({"Witness Role" ;"Change to 3 TYPE" ; "q" ;"^3 ROLE" ;"3 TYPE" ;nil ;"GFT " }) -- V4.0 GFT Should only apply when previous line is 2 _WITN addRule({"Witness Role" ;"Name Only to Fact Note" ; "a" ;"^2 _SHAN (.*)" ;doWitness ;"Witness Role" ;"HER LFT MFT RMT TNG" }) -- V1.6 for FH V6.0 -- V2.0 LFT RMT -- V3.1 TNG -- V4.0 HER MFT addRule({"Individual Record" ;"Save in Record Notes" ; "a" ;"^0 @(I)(%d+)@ .*" ;doAnyRecord ;nil ;"FTL+" }) -- V1.8 FTL -- V3.3 FTL+ addRule({"Family Record" ;"Save in Record Notes" ; "a" ;"^0 @(F)(%d+)@ .*" ;doAnyRecord ;nil ;"FTL+" }) -- V1.8 FTL -- V3.3 FTL+ addRule({"Individual Record" ;"Multi-Fact Instances" ; "p" ;"^0 @(I)%d+@" ;doMultiFact ;nil ;"TPT " }) -- V3.2 TPT addRule({"Family Record" ;"Multi-Fact Instances" ; "p" ;"^0 @(F)%d+@" ;doMultiFact ;nil ;"TPT " }) -- V3.2 TPT if IntFhVersion <= 6 then addRule({"Individual Record";"Add Fact Sort Dates" ; "q" ;"^0 @(I)%d+@" ;doSortDates ;nil ;"GST+ RMT+ " }) -- V3.3 GST+ RMT+ -- V4.4 Only in FH V6 addRule({"Family Record" ;"Add Fact Sort Dates" ; "q" ;"^0 @(F)%d+@" ;doSortDates ;nil ;"GST+ RMT+ " }) -- V3.3 GST+ RMT+ -- V4.4 Only in FH V6 else addRule({"Sort Date" ;"Remove Sort Date" ; "p" ;"^2 _SDATE .*" ; "" ;nil ;"Std " }) -- V4.0 addRule({"Sort Date" ;"Retain Sort Date" ; "p" ;"^2 _SDATE" ;"2 _SDATE" ;nil ;"GST RMT " }) -- V4.0 GST RMT support 2 _SDATE -- V4.2 FTM does not addRule({"Sort Date" ;"Move to LMO Sort Date" ; "p" ;"^2 _SDATE (.*)" ;doMove2LMO ;nil ;"FH5+ FH6+ " }) -- V4.0 FH5 FH6 make LMO Sort Date end local dicLegacyNotes = { Research="HI"; Medical="MI"; } addRule({"Media Record" ;"Save in Record Notes" ; "a" ;"^0 @(O)(%d+)@ .*" ;doAnyRecord ;nil ;"FTL+" }) -- V1.8 FTL -- V3.3 FTL+ See also doObjectId() and doLinkNote() addRule({"Media Record" ;"Adjust the Link/Notes" ; "p" ;"^0 @(O)(%d+)@ .*" ;doObjRecord ;"Caption Note" ;"Std fh5 fh6 ftl gst " }) -- V3.3 Adjust Media Record Link/Note captions and delete unused records, ftl, fh5 & gst kept -- V4.0 fh6 addRule({"Media Record" ;"Adjust the Link/Notes" ; "q" ;"^0 @(O)(%d+)@ .*" ;doObjRecord ;{"Caption Note";"M"} ;"FTL- FTM" }) -- V3.3 Adjust Media Record Link/Note captions and delete unused records, FTL- -- V4.0 FTM if IntFhVersion > 6 then addRule({"Media Record" ;"Family Historian V5/6" ; "r" ;"^0 @(O%d+)@ .*" ;doObjRec2FH ;nil ;"FH5 FH6" }) -- V4.0 Revert Media Record to FH V5/V6 format end addRule({"Note Record" ;"Save in Record Notes" ; "a" ;"^0 @(N)(%d+)@ .*" ;doAnyRecord ;nil ;"FTL+" }) -- V1.8 FTL -- V2.6 -- V3.3 FTL+ addRule({"Note Record" ;"Save in Record Notes" ; "q" ;"^0 @(N)(%d+)@ .*" ;doAnyRecord ;dicLegacyNotes ;"LFT " }) -- V2.6 LFT does not handle subsidiary Note Record tags & special Tab = RecId translation addRule({"Note Record" ;"Adjust Leading Line" ; "p" ;"^(0 @N%d+@ NOTE )(.*)";doNoteLine ;nil ;"FMP HER" }) -- V2.2 FMP -- V2.7 HER ignore first line of Note Record -- V3.3 addRule({"Source Record" ;"Save in Record Notes" ; "a" ;"^0 @(S)(%d+)@ .*" ;doAnyRecord ;nil ;"FTL+" }) -- V1.8 FTL -- V3.3 FTL+ addRule({"Repository Record" ;"Preserve Record Tags" ; "a" ;"^0 @(R)(%d+)@ .*" ;doRepoRec ;nil ;"FTL+" }) -- V1.8 FTL -- V3.3 FTL+ addRule({"Repository Record" ;"Save in Record Notes" ; "p" ;"^0 @(R)(%d+)@ .*" ;doRepoNote ;nil ;"ANC " }) -- V2.2 ANC does not upload Repository fields addRule({"Submitter Record" ;"Put in Source Record" ; "a" ;"^0 @(U)(%d+)@ .*" ;doSubm2Sour ;"Submitter" ;"FTL GSP HER LFT RFT" }) -- V1.8 FTL -- V2.0 GSP HER LFT -- V3.3 -- V3.9 RFT addRule({"Submission Record" ;"Put in Source Record" ; "a" ;"^0 @(B)(%d+)@ .*" ;doSubm2Sour ;"Submission" ;"FTL FTM GSP HER LFT RFT RMT" }) -- V1.8 FTL -- V2.0 GSP HER LFT -- v3.3 -- V3.9 RFT -- V4.0 FTM RMT addRule({"Place Record" ;"Put in Source Rec Title"; "A" ;"^0 @(P)(%d+)@ .*" ;doPlac2Sour ;"SOUR 1 TITL " ;"Std+" }) -- V1.6 for FH V6.0 see also doLinkNote() addRule({"Place Record" ;"Remove entirely" ; "F" ;"^(0) @P%d+@ .*" ;doRemoveAll ;nil ;"Std-" }) -- V1.6 for FH V6.0 not erased if in Named List -- V2.8 Mdr ZPG ??? addRule({"Place Record" ;"Put in Source Rec Abbr" ; "B" ;"^0 @(P)(%d+)@ .*" ;doPlac2Sour ;"SOUR 1 ABBR " ;"FH5 GSP " }) -- V2.0 swap ABBR & TITL -- V4.0/2 LFT moved to Gedcom 5.5.1 Lat/Long below addRule({"Place Record" ;"Save in Source Record" ; "C" ;"^0 @(P)(%d+)@ .*" ;doAnyRecord ;"SOUR 1 TITL " ;"FTL+" }) -- V1.6 for FH V6.0 -- V1.8 FTL -- V3.3 FTL+ addRule({"Place Record" ;"Gedcom 5.5.1 Lat/Long" ; "G" ;"^(0) @P%d+@ .*" ;doRemoveAll ;nil ;"Str AGS FTM GFT HER LFT MFT RFT CEB " }) -- V3.8 Str Gedcom 5.5.1 Lat/Long to Place field AGS CEB -- V4.0 FTM GFT HER LFT MFT addRule({"Place Record" ;"Delete Record Ident" ; "D" ;"^0 (@P%d+@ )(.*)" ;doPlaceTidy ;"NoRecId" ;"RMT TNG" }) -- V3.3 doPlaceTidy RMT addRule({"Place Record" ;"Keep Custom Record" ; "E" ;"^0 (@P%d+@ )(.*)" ;doPlaceTidy ;"OkRecId" ;"FH6 GST FTA" }) -- V1.6 for FH V6.0 Rule index " @P@" -- V3.1 GST -- V3.3 doPlaceTidy -- V3.8 FTA -- V4.0 FH6 addRule({"Standardized" ;"Move to Record Note" ; "p" ;"^1 STAN (.*)" ;doRecNote ;"Standardized" ;"RMT+" }) -- V4.0 RMT+ addRule({"Standardized" ;"Remove entirely" ; "q" ;"^(1) STAN .*" ;doRemoveAll ;nil ;"RMT-" }) -- V4.0 RMT- addRule({"Place Status" ;"Move to Record Note" ; "p" ;"^1 STAT (.*)" ;doRecNote ;"Place Status" ;"RMT+" }) -- V4.0 RMT+ addRule({"Place Status" ;"Remove entirely" ; "q" ;"^(1) STAT .*" ;doRemoveAll ;nil ;"RMT-" }) -- V4.0 RMT- addRule({"Place Field" ;"Cite Source via Place" ; "p" ;"^(%d) PLAC (.*)" ;doPlaceTag ;"Place Tidy" ;"Std ftl" }) -- V1.6 for FH V6.0 Rule index " PLAC" -- V1.8 ftl -- V2.2 "Place Tidy" -- V2.3 tng -- V3.1 gst -- V3.3 addRule({"Place Field" ;"Cite Source via Fact" ; "q" ;"^(%d) PLAC (.*)" ;doPlaceTag ;"Fact Tidy" ;"ANC GWC HER LFT MYH TPT" }) -- V1.6 for FH V6.0 Rule index " PLAC" -- V2.0 HER LFT "Fact" -- V2.2 ANC "Fact Tidy" -- V3.2 MYH TPT -- V3.3 GWC addRule({"Place Field" ;"Keep Standard Tag" ; "s" ;"^(%d) PLAC (.*)" ;doPlaceTag ;"Tidy" ;"FH6 GST TNG" }) -- V3.3 -- V4.0 FH6 addRule({"To/From Place" ;"Move to Fact Note" ; "A" ;"^2 _PLAC (.*)" ;do2ndPlace ;"Into/From Place Tidy" ;"Std+" }) -- V2.3 addRule({"To/From Place" ;"Remove entirely" ; "C" ;"^(2) _PLAC .*" ;doRemoveAll ;nil ;"Std-" }) -- V2.8 Mdr ZPG ??? addRule({"To/From Place" ;"Keep Custom Tag" ; "B" ;"^2 _PLAC (.*)" ;do2ndPlace ;"Tidy" ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V3.3 do2ndPlace;"Tidy" -- V4.0 FH6 addRule({"Date Field" ;"UPPER e.g. BEF JUN" ; "A" ;"^%d DATE .*" ;doDateField ;"UPPER" ;"Std " }) -- V1.4 Rule index " DATE" addRule({"Date Field" ;"Title e.g. Bef Jun" ; "B" ;"^%d DATE .*" ;doDateField ;"Title" ;"GSP " }) -- V1.4 -- V2.0 GSP addRule({"Date Field" ;"UPPER max length 37" ; "C" ;"^%d DATE .*" ;doDateField ;"About 37" ;"GRT " }) -- V2.0 GRT max length 37 addRule({"Date Field" ;"FROM/TO to AFT/BEF" ; "D" ;"^%d DATE .*" ;doDateField ;"FromToInterpret" ;"GFT HER" }) -- V4.0 GFT HER FROM/TO => AFT/BEF and Phrase to Note & INT to Date addRule({"Date Field" ;"Period to Range, etc" ; "E" ;"^%d DATE .*" ;doDateField ;"Period" ;"TPT " }) -- V3.2 TPT FROM/TO => AFT/BEF/BET AND and INT removed addRule({"Date Field" ;"Date Phrase to Note" ; "F" ;"^%d DATE .*" ;doDateField ;"Phrase" ;" " }) -- V4.0 LFT removed (only rejects ' in phrase) addRule({"Date Field" ;"Interpret Date Phrase" ; "G" ;"^%d DATE .*" ;doDateField ;"Interpret" ;"MYH " }) -- V4.0 MYH Phrase to Note & INT to Date addRule({"Phonetic Name" ;"Move to local Note" ; "p" ;"^(%d) FONE (.*)" ;doName2Note ;"Phonetic" ;"Std+" }) -- V4.0 addRule({"Phonetic Name" ;"Remove entirely" ; "p" ;"^(%d) FONE .*" ;doRemoveAll ;nil ;"Std-" }) -- V4.0 addRule({"Phonetic Name" ;"Keep Standard Tag" ; "p" ;"^(%d) FONE" ;"%1 FONE" ;nil ;"Str AGS GST CEB" }) -- V4.0 Most products do NOT support FONE: GFT GSP HER MFT RFT TNG addRule({"Romanised Name" ;"Move to local Note" ; "p" ;"^(%d) ROMN (.*)" ;doName2Note ;"Romanised" ;"Std+" }) -- V4.0 addRule({"Romanised Name" ;"Remove entirely" ; "p" ;"^(%d) ROMN .*" ;doRemoveAll ;nil ;"Std-" }) -- V4.0 addRule({"Romanised Name" ;"Keep Standard Tag" ; "p" ;"^(%d) ROMN" ;"%1 ROMN" ;nil ;"Str AGS GST CEB" }) -- V4.0 Most products do NOT support ROMN; GFT GSP HER MFT RFT TNG addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (AFN)(.*)" ;doCustEvent ;"INDI" ;" GWC HER MYH" }) -- V1.9 HER Ancestor File ignored -- V3.3 GWC -- V3.2 MYH addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (ALIA)(.*)" ;doCustEvent ;"INDI" ;" FMP GSP GWC HER LFT MYH RFT RMT TNG" }) -- V2.0 HER Alias Individual ignored -- V3.3 GWC -- V3.2 MYH -- V2.2 FMP -- V2.8 GSP v4.5.6 -- V3.9 RFT -- V4.0 LFT RMT TNG addRule({"Standard Attrib" ;"Revise Value" ; "r" ;"^1 (ALIA)(.*)" ;doRidValue ;"INDI" ;"ANC AQP " }) -- V2.2 ANC Alias Name allowed -- V4.0 AQP addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (ANCI)(.*)" ;doCustEvent ;"INDI" ;" FMP GSP GWC HER MYH RMT" }) -- V1.9 HER Ancestor Interest ignored -- V3.3 GWC -- V3.2 MYH -- V2.2 FMP -- V2.8 GSP v4.5.6 not recognised -- V4.0 RMT addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (DESI)(.*)" ;doCustEvent ;"INDI" ;" FMP GSP GWC HER MYH RMT" }) -- V1.9 HER Descendant Interest ignored -- V3.3 GWC -- V3.2 MYH -- V2.2 FMP -- V2.8 GSP v4.5.6 not recognised -- V4.0 RMT addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (DSCR)(.*)" ;doCustEvent ;"INDI" ;" AQP+ RFT+ " }) -- V3.8 AQP ignores DATE,NOTE,CAUS,TYPE,etc -- V3.9 RFT ditto -- addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (EDUC)(.*)" ;doCustEvent ;"INDI" ;" " }) -- V1.9 HER Education user field -- V4.0 HER removed OK addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (IDNO)(.*)" ;doCustEvent ;"INDI" ;"ANC AQP+ FTL FTM LFT RMT" }) -- V2.0 LFT -- V1.8 ANC FTL/FTM RMT Identity No ignored -- V3.8 AQP ignores DATE,NOTE,CAUS,TYPE,etc addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (NCHI)(.*)" ;doCustEvent ;"INDI, FAM" ;"ANC AQP FTL FTM RMT" }) -- V1.9 HER -- V1.8 ANC FTL/FTM RMT Child Count ignored -- V3.8 AQP ignored -- V4.0 HER removed OK addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (NMR)(.*)" ;doCustEvent ;"INDI" ;"ANC AQP FTL FTM RFT+ RMT" }) -- V2.0 HER -- V2.0 ANC FTL/FTM RMT Marr. Count ignored -- V3.8 AQP ignored -- V3.9 RFT -- V4.0 HER removed OK -- addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (PROP)(.*)" ;doCustEvent ;"INDI" ;" " }) -- V1.9 HER Possessions user field -- V4.0 HER removed OK addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (RESN)(.*)" ;doCustEvent ;"INDI, FAM" ;" RMT" }) -- V4.0 RMT addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (RIN)(.*)" ;doCustEvent ;"INDI, FAM" ;" FMP GWC MYH RMT" }) -- V1.9 HER Automated Id ignored but OK in User Field -- V3.3 GWC -- V3.2 MYH -- V2.2 FMP -- V2.4 RMT addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (RFN)(.*)" ;doCustEvent ;"INDI" ;" GWC MYH RMT" }) -- V1.9 HER Permanent Ref ignored but OK in User Field -- V3.3 GWC -- V3.2 MYH -- V4.0 RMT addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^%d (SUBM)(.*)" ;doCustEvent ;"INDI, FAM" ;" AQP FMP GSP GWC HER LFT MYH RFT RMT" }) -- V1.9 HER LFT Submitter not recognised -- V3.3 GWC -- V3.2 MYH -- V2.2 FMP -- V2.8 GSP v4.5.6 -- V3.8 AQP -- V3.9 RFT -- V4.0 RMT addRule({"Standard Attrib" ;"Custom Event" ; "p" ;"^1 (TITL)(.*)" ;doCustEvent ;"INDI" ;" AQP+ " }) -- V3.8 AQP ignores DATE,NOTE,CAUS,TYPE,etc addRule({"Standard Event" ;"Custom Event" ; "p" ;"^1 (CENS)(.*)" ;doCustEvent ;"FAM" ;" FTL FTM HER" }) -- V1.9 HER -- V1.8 FTL/FTM Census (family) ignored addRule({"Standard Event" ;"Custom Event" ; "q" ;"^1 (CENS)(.*)" ;doCustEvent ;"FAM, CENSUSFAMILY" ;" FMP" }) -- V2.2 FMP addRule({"Standard Event" ;"Custom Event" ; "p" ;"^1 (RESI)(.*)" ;doCustEvent ;"FAM" ;"Std str ags fmp ftm gst gsp gft her mft rmt rft ceb" }) -- V4.5 Residence (family) except GEDCOM 5.5.1 products and FindMyPast & RootsMagic addRule({"LDS Ordinance" ;"Custom Event" ; "p" ;"^1 (BAPL)(.*)" ;doCustEvent ;"INDI" ;" GWC RFT" }) -- V3.3 GWC -- V3.8 RFT addRule({"LDS Ordinance" ;"Custom Event" ; "p" ;"^1 (CONL)(.*)" ;doCustEvent ;"INDI" ;" GWC RFT" }) -- V3.3 GWC -- V3.8 RFT addRule({"LDS Ordinance" ;"Custom Event" ; "p" ;"^1 (ENDL)(.*)" ;doCustEvent ;"INDI" ;" GWC RFT" }) -- V3.3 GWC -- V3.8 RFT addRule({"LDS Ordinance" ;"Custom Event" ; "p" ;"^1 (SLGC)(.*)" ;doCustEvent ;"INDI" ;" GWC RFT" }) -- V3.3 GWC -- V3.8 RFT addRule({"LDS Ordinance" ;"Custom Event" ; "p" ;"^1 (SLGS)(.*)" ;doCustEvent ;"FAM" ;" GWC RFT" }) -- V3.3 GWC -- V3.8 RFT addRule({"Standard Attrib" ;"FACT tag & Sub Notes" ; "p" ;"^(1 CAST)(.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, ADDR, etc, to local Note addRule({"Standard Attrib" ;"FACT tag & Sub Notes" ; "p" ;"^(1 DSCR)(.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, ADDR, etc, to local Note addRule({"Standard Attrib" ;"FACT tag & Sub Notes" ; "p" ;"^(1 IDNO)(.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, ADDR, etc, to local Note addRule({"Standard Attrib" ;"FACT tag & Sub Notes" ; "p" ;"^(1 NATI)(.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, ADDR, etc, to local Note addRule({"Standard Attrib" ;"FACT tag & Sub Notes" ; "p" ;"^(1 NCHI)(.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, ADDR, etc, to local Note addRule({"Standard Attrib" ;"FACT tag & Sub Notes" ; "p" ;"^(1 SSN)(.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, ADDR, etc, to local Note addRule({"Baptism Event" ;"Change to Birth Event" ; "p" ;"^1 (BAPM)" ;doConvert ;"BIRT" ;"FTD+" }) -- V3.3 FTD+ addRule({"Christening Event" ;"Change to Birth Event" ; "p" ;"^1 (CHR)" ;doConvert ;"BIRT" ;"FTD+" }) -- V3.3 FTD+ addRule({"Burial Event" ;"Change to Death Event" ; "p" ;"^1 (BURI)" ;doConvert ;"DEAT" ;"FTD+" }) -- V3.3 FTD+ addRule({"Cremation Event" ;"Change to Death Event" ; "p" ;"^1 (CREM)" ;doConvert ;"DEAT" ;"FTD+" }) -- V3.3 FTD+ if IntFhVersion <= 6 then addRule({"Custom Attribute" ;"Custom Event & Note" ; "A" ;"^1 _ATTR (.*)" ;doCust_Attr ;"Attribute Value" ;"Std " }) addRule({"Custom Attribute" ;"Custom Fact & Value" ; "B" ;"^1 _ATTR (.*)" ;doCust_Attr ;nil ;"Str AGS ANC- FMP FTL FTM GSP GWC HER LFT MFT RFT RMT TNG ZPG CEB"}) -- V2.8 Str Mdr GSP -- V3.3 GWC LFT -- V3.8 AGS RFT CEB -- V4.0 FTM GFT HER MFT TNG -- TNG see http://www.tng.lythgoes.net/wiki/index.php?title=Desktop_gotchas#Family_Historian_.28FH.29 addRule({"Custom Attribute" ;"Keep Custom Tag" ; "C" ;"^1 _ATTR" ;"1 _ATTR" ;nil ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 addRule({"Custom Attribute" ;"FACT tag & Sub Notes" ; "E" ;"^(1 _ATTR) (.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, ADDR, etc, to local Note addRule({"Custom Attribute" ;"Remove entirely" ; "D" ;"^(1) _ATTR .*" ;doRemoveAll ;nil ;" " }) else addRule({"Custom Attribute" ;"Use 1 FACT Rule" ; "p" ;"^1 _ATTR (.*)" ;doFact_Rule ;nil ;"Std gst " }) -- V4.0 for Family _ATTR custom Attribute addRule({"Custom Attribute" ;"Custom Event & Note" ; "A" ;"^1 FACT (.*)" ;doCust_Attr ;"Attribute Value" ;"Std " }) -- V4.0 addRule({"Custom Attribute" ;"Custom Event & Value" ; "B" ;"^1 FACT (.*)" ;doCust_Attr ;nil ;"ANC- FMP FTL GWC LFT RMT TNG ZPG" }) -- V4.0 -- V4.4 GWC addRule({"Custom Attribute" ;"Keep Standard Tag" ; "C" ;"^1 FACT" ;"1 FACT" ;nil ;"Str AGS FTM GSP GST HER MFT RFT CEB" }) -- V4.0 GEDCOM 5.5.1 addRule({"Custom Attribute" ;"Keep Tag & Sub Notes" ; "E" ;"^(1 FACT) (.*)" ;doAttrNotes ;nil ;"GFT " }) -- V4.0 GFT subsidiary DATE, PLAC, etc to local Note addRule({"Custom Attribute" ;"Change to _ATTR" ; "F" ;"^1 FACT" ;"1 _ATTR" ;nil ;"FH5 FH6" }) -- V4.0 addRule({"Custom Attribute" ;"Remove entirely" ; "D" ;"^(1) FACT .*" ;doRemoveAll ;nil ;" " }) -- V4.0 end addRule({"Associated Person" ;"Custom Event + Note" ; "p" ;"^1 ASSO @(I%d+)@" ;doAssociate ;"Associate" ;"ANC+ FMP GWC LFT MYH RFT RMT RWW TPT" }) -- V2.0 -- V2.2 FMP -- V2.5 ANC LFT RWW -- V3.2 MYH TPT -- V3.3 GWC -- V3.9 RFT addRule({"Associated Person" ;"Custom Event" ; "q" ;"^1 ASSO @(I%d+)@" ;doAssociate ;nil ;"ANC- AQP FTL FTM" }) -- V2.0 -- V3.8 AQP local arrCustomId_p = {"Custom Ident";" NOTE, _RNOT";} local arrCustomId_r = {"Custom Ident";" NOTE, SOUR, REPO, _RNOT";} local arrCustomId_s = {"Custom Ident";" NOTE, REPO, _RNOT";} local arrCustomId_t = {"Custom Ident";"FAM, NOTE, REPO, _RNOT";} local arrCustomId_u = {"Custom Ident";"FAM, NOTE, SOUR, REPO, _RNOT";} local arrAutomated_s = {"Automated Id";"NOTE, REPO, _RNOT";} addRule({"Associated Reltn" ;"Move to Fact Note" ; "p" ;"^2 RELA (.*)" ;doFactNote ;"Relationship" ;"ANC FMP FTL FTM GWC LFT MYH RMT RWW TPT" }) -- V2.5 ANC LFT RWW -- V3.2 MYH TPT -- V3.3 GWC addRule({"Custom Ident" ;"Move to Record Note" ; "p" ;"^1 REFN (.*)" ;doCustomId ;arrCustomId_p ;"RMT " }) -- V1.3 GFT -- V2.4 RMT -- V4.0 GFT moved to Rule "s" addRule({"Custom Ident" ;"Move to Record Note" ; "q" ;"^1 REFN (.*)" ;doCustomId ;{"Custom Ident";"ALL";} ;"GWC MYH TPT" }) -- V3.2 MYH TPT -- V3.3 GWC addRule({"Custom Ident" ;"Move to Record Note" ; "r" ;"^1 REFN (.*)" ;doCustomId ;arrCustomId_r ;"FTM " }) -- V4.0 FTM addRule({"Custom Ident" ;"Move to Record Note" ; "s" ;"^1 REFN (.*)" ;doCustomId ;arrCustomId_s ;"GFT " }) -- V4.0 GFT addRule({"Custom Ident" ;"Move to Record Note" ; "t" ;"^1 REFN (.*)" ;doCustomId ;arrCustomId_t ;"HER " }) -- V4.0 HER addRule({"Custom Ident" ;"Move to Record Note" ; "u" ;"^1 REFN (.*)" ;doCustomId ;arrCustomId_u ;"AQP " }) -- V3.8 AQP addRule({"Automated Id" ;"Move to Record Note" ; "s" ;"^1 RIN (.*)" ;doCustomId ;arrAutomated_s ;"FTM GFT HER" }) -- V4.0 FTM GFT HER addRule({"Type Descriptor" ;"Move to Fact Note" ; "p" ;"^2 TYPE (.*)" ;doFactType ;"Descriptor" ;"ANC FMP GFT GWC LFT MYH RMT RWW TPT" }) -- V1.8 All -- V1.3 GFT -- V2.5 ANC LFT RWW -- V3.2 MYH TPT -- V3.3 GWC addRule({"Type Descriptor" ;"Remove selectively" ; "p" ;"^2 TYPE (.*)" ;doFactType ;nil ;"AQP FTL FTM" }) -- V2.2 FTL/FTM moved here -- V3.8 AQP addRule({"Type Descriptor" ;"Remove from ASSO" ; "p" ;"^2 TYPE (.*)" ;doAssoType ;nil ;"Std " }) -- V3.8 Remove 2 TYPE from 1 ASSO addRule({"Relationship" ;"Change to 1 _FIL" ; "p" ;"^2 PEDI (.*)" ;doRelation ;"1 _FIL " ;"HER " }) -- V1.9 HER addRule({"Relationship" ;"Change to 1 _FIL" ; "p" ;"^2 _PEDI (.*)" ;doRelation ;"1 _FIL " ;"HER " }) -- V1.9 HER addRule({"Relationship" ;"Move to Record Note" ; "q" ;"^2 PEDI (.*)" ;doPedigree ;"Relationship" ;"FMP FTL- FTM GSP gst" }) -- V2.0 GSP -- V2.2 FMP -- V1.3 gst -- V3.3 FTL-/FTM addRule({"Relationship" ;"Move to Record Note" ; "q" ;"^2 _PEDI (.*)" ;doPedigree ;"Relationship" ;"Std fh5 fh6 gst her " }) -- V1.8 Std -- V1.3 gst & fh5 -- V4.0 fh6 addRule({"Fact Cause" ;"Remove unless Death" ; "p" ;"^2 CAUS (.*)" ;doFactCause ;nil ;"FTL FTM" }) -- V1.8 FTL/FTM addRule({"Fact Cause" ;"Fact Note unless Death" ; "q" ;"^2 CAUS (.*)" ;doFactCause ;"Fact Cause" ;"FMP LFT MYH RMT TPT" }) -- V2.0 LFT -- V2.2 FMP -- V3.2 MYH TPT -- V4.0 RMT addRule({"Fact Flags" ;"Move to Fact Note" ; "p" ;"^(2) _FLGS" ;doFlagNote ;"Fact Flags" ;"Std+" }) -- V4.0 Can any products use 3 __PREFERRED Preferred addRule({"Fact Flags" ;"Keep Custom Tag" ; "p" ;"^2 _FLGS" ;"2 _FLGS" ;nil ;"GST " }) -- V4.0 addRule({"Fact Flags" ;"Remove entirely" ; "p" ;"^(2) _FLGS" ;doRemoveAll ;nil ;"Std-" }) -- V4.0 addRule({"Fact Address" ;"Keep Standard Tag" ; "A" ;"^(2) ADDR (.*)" ;doAddrTidy ;nil ;"Std " }) -- V3.3 (2) -- V2.3 addRule({"Fact Address" ;"Move to Fact Note" ; "B" ;"^2 ADDR (.*)" ;doFactNote ;"Fact Address" ;"ANC+ HER ZPG" }) -- V3.3 2 doFactNote -- V2.0 HER -- V2.1 ZPG -- V2.2 ANC -- V2.3 doAddrNote addRule({"Fact Address" ;"Move to Event Value" ; "C" ;"^2 ADDR (.*)" ;doAddr2Even ;"Fact Address" ;"FTL FTM" }) -- V3.3 2 doAddr2Even -- V1.8 FTL/FTM addRule({"Fact Address" ;"Move to Place Field" ; "D" ;"^2 ADDR (.*)" ;doAddr2Plac ;"PLAC" ;"ANC- AQP FST MYH RFT TPT" }) -- V3.3 2 doAddr2Plac -- V3.2 MYH TPT -- V3.3 FST -- V3.8 AQP -- V3.9 RFT addRule({"Record Address" ;"Keep Standard Tag" ; "p" ;"^(1) ADDR (.*)" ;doAddrTidy ;nil ;"Std " }) -- V3.3 addRule({"Record Address" ;"Move to Record Note" ; "q" ;"^1 ADDR (.*)" ;doRecNote ;"Postal Address" ;"GWC " }) -- V3.3 GWC -- Repository OK but Submitter uses doPutOther() with Arg = "PHON" addRule({"Same Sex Husband" ;"Asymmetric Format" ; "a" ;"^1 (HUSB) (.*)" ;doSameGender;"1 WIFE " ;"Std fh5 fh6 gst" }) -- V2.6 -- V3.1 gst & fh5 -- V4.0 fh6 addRule({"Same Sex Wife" ;"Asymmetric Format" ; "a" ;"^1 (WIFE) (.*)" ;doSameGender;"1 HUSB " ;"Std fh5 fh6 gst" }) -- V2.6 -- V3.1 gst & fh5 -- V4.0 fh6 addRule({"Fact Sentence" ;"Remove entirely" ; "p" ;"^%d _SENT .*" ; "" ;nil ;"Std" }) -- V3.1 fh5 removed addRule({"Fact Sentence" ;"Keep Custom Tag" ; "q" ;"^(%d) _SENT" ;"%1 _SENT" ;nil ;"FH5 FH6 GST" }) -- V3.1 FH5 GST -- V4.0 FH6 addRule({"Fact Husband Age" ;"Move to Fact Note" ; "a" ;"^2 HUSB" ;doSpouseAge ;"Male Partner Age" ;"ANC AQP FMP FTL- FTM GSP GWC RFT RMT TPT" }) -- V2.0 GSP -- V2.2 ANC FMP -- V3.2 TPT -- V3.3 FTL- GWC -- V3.8 AQP -- V3.9 RFT addRule({"Fact Wife Age" ;"Move to Fact Note" ; "a" ;"^2 WIFE" ;doSpouseAge ;"Lady Partner Age" ;"ANC AQP FMP FTL- FTM GSP GWC RFT RMT TPT" }) -- V2.0 GSP -- V2.2 ANC FMP -- V3.2 TPT -- V3.3 FTL- GWC -- V3.8 AQP -- V3.9 RFT addRule({"Fact Person Age" ;"Move to Fact Note" ; "a" ;"^2 AGE (.*)" ;doFactNote ;"Individual's Age" ;"ANC AQP FMP FTL- FTM GSP GWC RFT RMT TPT" }) -- V2.0 GSP -- V2.2 ANC FMP -- V3.2 TPT -- V3.3 FTL- GWC -- V3.8 AQP -- V3.9 RFT addRule({"Census Event" ;"Change to Residence" ; "a" ;"^1 (CENS)" ;doCens2Resi ;"RESI" ;"ANC " }) -- V3.4 ANC if IntFhVersion <= 6 then addRule({"Fact E-mail" ;"Move to Fact Note" ; "A" ;"^2 _EMAIL (.*)" ;doFactNote ;"E-mail Address" ;"Std+" }) addRule({"Fact E-mail" ;"Remove entirely" ; "F" ;"^2 _EMAIL .*" ; "" ;nil ;"Std-" }) addRule({"Fact E-mail" ;"Gedcom 5.5.1 EMAIL" ; "C" ;"^2 _EMAIL (.*)" ;doContacts ;"2 EMAIL " ;"Str AGS GFT LFT MFT RFT TNG CEB" }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT LFT MFT TNG addRule({"Fact E-mail" ;"Demote to 3 EMAIL" ; "D" ;"^2 _EMAIL" ;"3 EMAIL" ;nil ;" " }) -- V4.0 TNG removed addRule({"Fact E-mail" ;"Append to 2 PHON" ; "B" ;"^(2) _EMAIL (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V1.8 ZPG -- V4.0 GSP added & ZPG removed addRule({"Fact E-mail" ;"Keep Custom Tag" ; "E" ;"^2 _EMAIL" ;"2 _EMAIL" ;nil ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 addRule({"Record E-mail" ;"Move to Record Note" ; "A" ;"^1 _EMAIL (.*)" ;doRecNote ;"E-mail Address" ;"Std+" }) -- Repository OK but Submitter uses doPutOther() with Arg = "PHON" addRule({"Record E-mail" ;"Remove entirely" ; "G" ;"^1 _EMAIL .*" ; "" ;nil ;"Std-" }) addRule({"Record E-mail" ;"Gedcom 5.5.1 EMAIL" ; "D" ;"^1 _EMAIL" ;"1 EMAIL" ;nil ;"Str AGS GFT LFT MFT MYH RFT TNG ZPG CEB" }) -- V1.8 FTL -- V2.8 Str Mdr -- V3.2 MYH -- V3.8 HER~2019 AGS RFT CEB -- V4.0 GFT LFT MFT TNG & FTL removed addRule({"Record E-mail" ;"Demote to 2 EMAIL" ; "E" ;"^1 _EMAIL" ;"2 EMAIL" ;nil ;" " }) -- V4.0 TNG removed addRule({"Record E-mail" ;"Append to 1 EMAIL" ; "C" ;"^(1) _EMAIL (.*)" ;doPutOther ;"EMAIL" ;"FTL FTM HER RMT" }) -- V1.8 FTL -- V3.3 RMT -- V4.0 FTM HER addRule({"Record E-mail" ;"Append to 1 PHON" ; "B" ;"^(1) _EMAIL (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V1.8 -- V4.0 GSP addRule({"Record E-mail" ;"Keep Custom Tag" ; "F" ;"^1 _EMAIL" ;"1 _EMAIL" ;nil ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 else addRule({"Fact E-mail" ;"Move to Fact Note" ; "A" ;"^2 EMAIL (.*)" ;doFactNote ;"E-mail Address" ;"Std+" }) addRule({"Fact E-mail" ;"Remove entirely" ; "F" ;"^2 EMAIL .*" ; "" ;nil ;"Std-" }) addRule({"Fact E-mail" ;"Gedcom 5.5.1 EMAIL" ; "C" ;"^2 EMAIL (.*)" ;doContacts ;"2 EMAIL " ;"Str AGS GFT GST LFT MFT RFT TNG CEB " }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT GST LFT MFT TNG addRule({"Fact E-mail" ;"Demote to 3 EMAIL" ; "D" ;"^2 EMAIL" ;"3 EMAIL" ;nil ;" " }) -- V4.0 TNG removed addRule({"Fact E-mail" ;"Append to 2 PHON" ; "B" ;"^(2) EMAIL (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V1.8 ZPG -- V4.0 GSP added & ZPG removed addRule({"Fact E-mail" ;"Change to 2 _EMAIL" ; "E" ;"^2 EMAIL" ;"2 _EMAIL" ;nil ;"FH5 FH6" }) -- V4.0 FH5 FH6 addRule({"Record E-mail" ;"Move to Record Note" ; "A" ;"^1 EMAIL (.*)" ;doRecNote ;"E-mail Address" ;"Std+" }) -- Repository OK but Submitter uses doPutOther() with Arg = "PHON" addRule({"Record E-mail" ;"Remove entirely" ; "G" ;"^1 EMAIL .*" ; "" ;nil ;"Std-" }) addRule({"Record E-mail" ;"Gedcom 5.5.1 EMAIL" ; "D" ;"^1 EMAIL" ;"1 EMAIL" ;nil ;"Str AGS GFT GST LFT MFT MYH RFT TNG ZPG CEB" }) -- V1.8 FTL -- V2.8 Str Mdr -- V3.2 MYH -- V3.8 HER AGS RFT CEB -- V4.0 GFT GST LFT MFT TNG & FTL removed addRule({"Record E-mail" ;"Demote to 2 EMAIL" ; "E" ;"^1 EMAIL" ;"2 EMAIL" ;nil ;" " }) -- V4.0 TNG removed addRule({"Record E-mail" ;"Append to 1 EMAIL" ; "C" ;"^(1) EMAIL (.*)" ;doPutOther ;"EMAIL" ;"FTL FTM HER RMT" }) -- V1.8 FTL -- V3.3 RMT -- V4.0 FTM HER addRule({"Record E-mail" ;"Append to 1 PHON" ; "B" ;"^(1) EMAIL (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V1.8 -- V4.0 GSP addRule({"Record E-mail" ;"Change to 1 _EMAIL" ; "F" ;"^1 EMAIL" ;"1 _EMAIL" ;nil ;"FH5 FH6" }) -- V4.0 FH5 FH6 end if IntFhVersion <= 6 then addRule({"Fact Website" ;"Move to Fact Note" ; "A" ;"^2 _WEB (.*)" ;doFactNote ;"Website Address" ;"Std+" }) addRule({"Fact Website" ;"Remove entirely" ; "E" ;"^2 _WEB .*" ; "" ;nil ;"Std-" }) addRule({"Fact Website" ;"Gedcom 5.5.1 WWW" ; "B" ;"^2 _WEB (.*)" ;doContacts ;"2 WWW " ;"Str AGS GFT LFT MFT RFT TNG CEB" }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT LFT MFT TNG addRule({"Fact Website" ;"Demote to 3 WWW" ; "C" ;"^2 _WEB" ;"3 WWW" ;nil ;" " }) -- V4.0 TNG removed addRule({"Fact Website" ;"Append to 2 EMAIL" ; "G" ;"^(2) _WEB (.*)" ;doPutOther ;"EMAIL" ;" " }) -- V1.8 FTL -- V4.0 FTL removed addRule({"Fact Website" ;"Append to 2 PHON" ; "F" ;"^(2) _WEB (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V4.0 GSP addRule({"Fact Website" ;"Keep Custom Tag" ; "D" ;"^2 _WEB" ;"2 _WEB" ;nil ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 addRule({"Record Website" ;"Move to Record Note" ; "A" ;"^1 _WEB (.*)" ;doRecNote ;"Website Address" ;"Std+" }) -- Repository OK but Submitter uses doPutOther() with Arg = "PHON" addRule({"Record Website" ;"Remove entirely" ; "F" ;"^1 _WEB .*" ; "" ;nil ;"Std-" }) addRule({"Record Website" ;"Gedcom 5.5.1 WWW" ; "C" ;"^1 _WEB" ;"1 WWW" ;nil ;"Str AGS GFT LFT MFT MYH RFT RMT TNG ZPG CEB" }) -- V2.8 Str Mdr -- V3.2 MYH -- V3.3 RMT -- V3.8 HER AGS RFT CEB -- V4.0 GFT LFT MFT TNG addRule({"Record Website" ;"Demote to 2 WWW" ; "D" ;"^1 _WEB" ;"2 WWW" ;nil ;" " }) -- V4.0 TNG removed addRule({"Record Website" ;"Append to 1 WWW" ; "G" ;"^(1) _WEB (.*)" ;doPutOther ;"WWW" ;"HER " }) -- V4.0 HER addRule({"Record Website" ;"Append to 1 EMAIL" ; "B" ;"^(1) _WEB (.*)" ;doPutOther ;"EMAIL" ;"FTL FTM" }) -- V1.8 FTL FTM addRule({"Record Website" ;"Append to 1 PHON" ; "H" ;"^(1) _WEB (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V1.8 FTL addRule({"Record Website" ;"Keep Custom Tag" ; "E" ;"^1 _WEB" ;"1 _WEB" ;nil ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 else addRule({"Fact Website" ;"Move to Fact Note" ; "A" ;"^2 WWW (.*)" ;doFactNote ;"Website Address" ;"Std+" }) addRule({"Fact Website" ;"Remove entirely" ; "E" ;"^2 WWW .*" ; "" ;nil ;"Std-" }) addRule({"Fact Website" ;"Gedcom 5.5.1 WWW" ; "B" ;"^2 WWW (.*)" ;doContacts ;"2 WWW " ;"Str AGS GFT GST LFT MFT RFT TNG CEB " }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT GST LFT MFT TNG ~ GSP? addRule({"Fact Website" ;"Demote to 3 WWW" ; "C" ;"^2 WWW" ;"3 WWW" ;nil ;" " }) -- V4.0 TNG removed addRule({"Fact Website" ;"Append to 2 EMAIL" ; "G" ;"^(2) WWW (.*)" ;doPutOther ;"EMAIL" ;" " }) -- V1.8 FTL -- V4.0 FTL removed addRule({"Fact Website" ;"Append to 2 PHON" ; "F" ;"^(2) WWW (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V4.0 GSP addRule({"Fact Website" ;"Change to 2 _WEB" ; "D" ;"^2 WWW" ;"2 _WEB" ;nil ;"FH5 FH6" }) -- V4.0 FH5 FH6 addRule({"Record Website" ;"Move to Record Note" ; "A" ;"^1 WWW (.*)" ;doRecNote ;"Website Address" ;"Std+" }) -- Repository OK but Submitter uses doPutOther() with Arg = "PHON" addRule({"Record Website" ;"Remove entirely" ; "F" ;"^1 WWW .*" ; "" ;nil ;"Std-" }) addRule({"Record Website" ;"Gedcom 5.5.1 WWW" ; "C" ;"^1 WWW" ;"1 WWW" ;nil ;"Str AGS GFT GST LFT MFT MYH RFT RMT TNG ZPG CEB" }) -- V2.8 Str Mdr -- V3.2 MYH -- V3.3 RMT -- V3.8 HER~2019 AGS RFT CEB -- V4.0 GFT GST HER LFT MFT TNG ~ GSP? addRule({"Record Website" ;"Demote to 2 WWW" ; "D" ;"^1 WWW" ;"2 WWW" ;nil ;" " }) -- V4.0 TNG removed addRule({"Record Website" ;"Change to 1 _WEB" ; "E" ;"^1 WWW" ;"1 _WEB" ;nil ;"FH5 FH6" }) -- V4.0 FH5 FH6 addRule({"Record Website" ;"Append to 1 WWW" ; "B" ;"^(1) WWW (.*)" ;doPutOther ;"WWW" ;"HER " }) -- V4.0 HER addRule({"Record Website" ;"Append to 1 EMAIL" ; "G" ;"^(1) WWW (.*)" ;doPutOther ;"EMAIL" ;"FTL FTM" }) -- V1.8 FTL FTM addRule({"Record Website" ;"Append to 1 PHON" ; "H" ;"^(1) WWW (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V1.8 FTL -- V4.0 FTL removed end if IntFhVersion > 6 then addRule({"Fact Fax No." ;"Move to Fact Note" ; "A" ;"^2 FAX (.*)" ;doFactNote ;"Fax Number" ;"Std+" }) -- V4.0 addRule({"Fact Fax No." ;"Remove entirely" ; "E" ;"^2 FAX .*" ; "" ;nil ;"Std-" }) -- V4.0 addRule({"Fact Fax No." ;"Gedcom 5.5.1 FAX" ; "B" ;"^2 FAX (.*)" ;doContacts ;"2 FAX " ;"Str AGS GFT GST MFT RFT CEB" }) -- V4.0 addRule({"Fact Fax No." ;"Demote to 3 FAX" ; "C" ;"^2 FAX" ;"3 FAX" ;nil ;" " }) -- V4.0 TNG removed addRule({"Fact Fax No." ;"Append to 2 PHON" ; "F" ;"^(2) FAX (.*)" ;doPutOther ;"PHON" ;"GSP " }) -- V4.0 GSP addRule({"Fact Fax No." ;"Change to 2 PHON" ; "D" ;"^2 FAX" ;"2 PHON" ;nil ;"FH5 FH6 LFT TNG" }) -- V4.0 FH5 FH6 LFT TNG addRule({"Record Fax No." ;"Move to Record Note" ; "A" ;"^1 FAX (.*)" ;doRecNote ;"Fax Number" ;"Std+" }) -- Repository OK but Submitter uses doPutOther() with Arg = "PHON" addRule({"Record Fax No." ;"Remove entirely" ; "F" ;"^1 FAX .*" ; "" ;nil ;"Std-" }) -- V4.0 addRule({"Record Fax No." ;"Gedcom 5.5.1 FAX" ; "C" ;"^1 FAX" ;"1 FAX" ;nil ;"Str AGS GST MFT MYH RFT RMT ZPG CEB" }) -- V4.0 addRule({"Record Fax No." ;"Demote to 2 FAX" ; "D" ;"^1 FAX" ;"2 FAX" ;nil ;" " }) -- V4.0 TNG removed addRule({"Record Fax No." ;"Append to 1 PHON" ; "B" ;"^(1) FAX (.*)" ;doPutOther ;"PHON" ;"FTL FTM GFT GSP HER" }) -- V4.0 FTL FTM GFT GSP HER addRule({"Record Fax No." ;"Change to 1 PHON" ; "E" ;"^1 FAX" ;"1 PHON" ;nil ;"FH5 FH6 LFT TNG" }) -- V4.0 FH5 FH6 LFT TNG end addRule({"Fact Phone" ;"Keep Standard Tag" ; "A" ;"^2 PHON (.*)" ;doContacts ;"2 PHON " ;"Std LFT TNG" }) -- V1.8 -- V3.3 LFT -- V4.0 TNG addRule({"Fact Phone" ;"Move to Fact Note" ; "B" ;"^2 PHON (.*)" ;doFactNote ;"Phone Number" ;"AQP FTL FTM GWC HER MYH RMT TPT ZPG" }) -- V1.8 FTL -- V3.2 MYH TPT -- V3.3 GWC -- V3.8 AQP -- V4.0 FTM HER RMT ZPG addRule({"Fact Phone" ;"Demote to 3 PHON" ; "C" ;"^2 PHON" ;"3 PHON" ;nil ;" " }) -- V4.0 TNG removed addRule({"Record Phone" ;"Keep Standard Tag" ; "A" ;"^1 PHON" ;"1 PHON" ;nil ;"Std TNG" }) -- V1.8 -- V4.0 TNG addRule({"Record Phone" ;"Move to Record Note" ; "D" ;"^1 PHON (.*)" ;doRecNote ;"Phone Number" ;"GWC " }) -- V3.3 GWC addRule({"Record Phone" ;"Demote to 2 PHON" ; "C" ;"^1 PHON" ;"2 PHON" ;nil ;" " }) -- V4.0 TNG removed addRule({"Record Phone" ;"Append to 1 PHON" ; "B" ;"^(1) PHON (.*)" ;doPutOther ;"PHON" ;"FTL FTM GFT" }) -- V1.8 FTL -- V4.0 FTM GFT addRule({"Record Note" ;"Keep Standard Tag" ; "E" ;"^1 NOTE " ;"1 NOTE " ;nil ;"Std " }) -- V3.3 addRule({"Record Note" ;"Adjust Note Link" ; "A" ;"^(1) NOTE (.*)" ;doNoteLink ;nil ;"LFT " }) -- V3.3 Only for Legacy Notes now -- V2.5 "Caption Note" & ftl addRule({"Record Note" ;"Move to FAMSTORY Fact" ; "B" ;"^1 NOTE (.*)" ;doNote2Fact ;"FAMSTORY" ;"FMP " }) -- V3.3 -- V2.2 FMP addRule({"Record Note" ;"Move to Fam Note Fact" ; "C" ;"^1 NOTE (.*)" ;doNote2Fact ;"Fam Note" ;" " }) -- V3.3 addRule({"Record Note" ;"Citation Note" ; "D" ;"^(1) NOTE (.*)" ;doCiteNote ;nil ;"ANC " }) -- V3.3 -- V2.2 ANC addRule({"Subsidiary Note" ;"Keep Standard Tag" ; "C" ;"^(%d) NOTE " ;"%1 NOTE " ;nil ;"Std " }) -- V3.3 addRule({"Subsidiary Note" ;"Media Picture Note" ; "D" ;"^(%d) NOTE " ;doRecNote ;"Picture Note" ;"GFT " }) -- V4.0 "Picture Note" for GFT addRule({"Subsidiary Note" ;"Adjust Note Link" ; "A" ;"^(%d) NOTE (.*)" ;doNoteLink ;nil ;"LFT " }) -- V3.3 Only for Legacy Notes now -- V2.5 "Caption Note" & ftl addRule({"Subsidiary Note" ;"Citation Note" ; "B" ;"^(%d) NOTE (.*)" ;doCiteNote ;nil ;"ANC " }) -- V3.3 -- V2.2 ANC addRule({"Subsidiary Note" ;"Individual Name Note" ; "E" ;"^(%d) NOTE (.*)" ;doNameNote ;"Name Note" ;"HER " }) -- V4.0 HER addRule({"Subsidiary Note" ;"Weblink to _LINK URL" ; "F" ;"^(%d) NOTE (.*)" ;doWeblink ;nil ;"FTM " }) -- V4.6 if IntFhVersion > 6 then addRule({"Auto Text Mode" ;"Remove entirely" ; "p" ;"^%d _AUTO .*" ; "" ;nil ;"Std gst" }) -- V4.0 Auto Text Mode addRule({"Textual Format" ;"Remove entirely" ; "p" ;"^%d _FMT .*" ; "" ;nil ;"Std gst" }) -- V4.0 Textual Format addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_I .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_F .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_N .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_S .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_R .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_O .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_P .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_B .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_U .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Link" ;"Remove entirely" ; "q" ;"^%d _LINK_E .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Rich Text Ident" ;"Remove entirely" ; "r" ;"^%d _LKID .*" ; "" ;nil ;"Std gst" }) -- V4.0 addRule({"Repository Link" ;"Move to Record Note" ; "p" ;"^1 REPO (.*)" ;doRepoLink ;"Repository" ;"Std str ags ftm gft gst her mft rft tng cem" }) -- V4.0 Multi-instance Repository links disallowed in GEDCOM 5.5 end local dicCaption = -- Media Object Conversion Caption Action Arguments -- V3.3 { Norm = { Lab="Caption Note"; }; Keep = { Lab="Caption Note"; Mode="Keep"; }; ABS = { Lab="Caption Note"; Mode="ABS" ; Pref=" _PROF" ; Word="FindMyPast"; }; All = { Lab="Caption Note"; Mode="All" ; Pref=" _PRIM Y"; }; LM1 = { Lab="Caption Note"; Mode="LMO" ; Pref=" _PRIM Y"; }; LM2 = { Lab="Caption Note"; Mode="LMO" ; Pref=" _PRIM Y\n _PRIM_CUTOUT Y\n _POSITION 0 0 {W} {H}\n _FILESIZE {S}\n OBJE\n FORM {F}\n TITL {T}\n FILE {N}\n _PRIM Y\n _CUTOUT Y\n _FILESIZE {S}"; }; O2M = { Lab="Caption Note"; Mode="Tag" ; Pref=" _PHOTO" ; Init="M"; }; } addRule({"Media Object" ;"Media Conversion" ; "p" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.Norm ;"Std " }) -- V1.3 Rule index " OBJE" -- V2.0 ftl -- V2.1 tng addRule({"Media Object" ;"Media Conversion" ; "t" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.Keep ;"FH5 FH6 GST" }) -- V3.3 dicCaption -- V4.0 FH6 addRule({"Media Object" ;"Media Conversion" ; "q" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.ABS ;"FMP " }) -- V1.3 Rule index " OBJE" -- V2.1 Arg table added -- V3.3 FMP addRule({"Media Object" ;"Media Conversion" ; "r" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.All ;"TNG " }) -- V1.3 Rule index " OBJE" -- V2.5 L="Caption Note"; -- V2.1 TNG addRule({"Media Object" ;"Media Conversion" ; "v" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.LM1 ;"AQP LFT RMT" }) -- V1.3 Rule index " OBJE" -- V2.5 L="Caption Note"; -- V3.3 LFT RMT -- V3.8 AQP addRule({"Media Object" ;"Media Conversion" ; "s" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.LM2 ;"MYH " }) -- V1.3 Rule index " OBJE" -- V3.2 MYH addRule({"Media Object" ;"Media Conversion" ; "u" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.O2M ;"FTL FTM" }) -- V1.3 Rule index " OBJE" -- V2.0 FTL/FTM -- V2.1 Arg table added if IntFhVersion <= 6 then addRule({"Picture Note" ;"Move to local Note" ; "A" ;"^(%d) _NOTE (.*)" ;doLocNote ;"Picture Note" ;"Std+ FTL-" }) -- V1.3 doLocNote -- V3.3 FTL- addRule({"Picture Note" ;"Remove entirely" ; "D" ;"^(%d) _NOTE .*" ;doRemoveAll ;nil ;"Std- FTL+ ftl" }) -- V1.3 -- V1.8 FTL -- V2.8 Mdr -- V3.3 FTL+ ftl addRule({"Picture Note" ;"Move to Note Record" ; "B" ;"^(%d) _NOTE .*" ;doObjNote ;"Picture Note" ;" " }) -- V1.3 GFT disallows Local Object Notes -- V4.0 GFT removed addRule({"Picture Note" ;"Keep Custom Tag" ; "C" ;"^(%d) _NOTE" ;"%1 _NOTE" ;nil ;"FH5 FH6 GST" }) -- V1.3 Rule index " _NOTE" -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 else addRule({"Picture Note" ;"Move to local Note" ; "A" ;"^(%d) _NOTA (.*)" ;doLoc_Nota ;"Picture Note" ;"Std+ FTL-" }) -- V1.3 doLocNote -- V3.3 FTL- addRule({"Picture Note" ;"Remove entirely" ; "D" ;"^(%d) _NOTA .*" ;doRemoveAll ;nil ;"Std- FTL+ ftl" }) -- V1.3 -- V1.8 FTL -- V2.8 Mdr -- V3.3 FTL+ ftl addRule({"Picture Note" ;"Move to Note Record" ; "B" ;"^(%d) _NOTA .*" ;doObj_Nota ;"Picture Note" ;" " }) -- V1.3 doObjNote as GFT does not support Local Object Notes addRule({"Picture Note" ;"Keep Custom Tag" ; "C" ;"^(%d) _NOTA" ;"%1 _NOTA" ;nil ;"GST " }) -- V3.1 GST addRule({"Picture Note" ;"Move to _NOTE" ; "E" ;"^(%d) _NOTA" ;"%1 _NOTE" ;nil ;"FH5 FH6" }) -- V4.0 FH5 FH6 end addRule({"Media Keywords" ;"Move to local Note" ; "A" ;"^(%d) _KEYS (.*)" ;doLocNote ;"Keywords" ;"Std+" }) -- V1.3 doLocNote Rule index " _KEYS" addRule({"Media Keywords" ;"Remove entirely" ; "D" ;"^%d _KEYS .*" ; "" ;nil ;"Std- FTL" }) -- V1.3 -- V2.8 Mdr addRule({"Media Keywords" ;"Move to Note & _TYPE" ; "E" ;"^(%d) _KEYS (.*)" ;doKeyword ;{"Keywords";" _TYPE ";} ;"TNG " }) -- V2.1 doKeyword as TNG needs _TYPE code addRule({"Media Keywords" ;"Move to Note Record" ; "B" ;"^(%d) _KEYS .*" ;doObjNote ;"Keywords" ;" " }) -- V1.3 GFT disallows Local Object Notes -- V4.0 GFT removed addRule({"Media Keywords" ;"Keep Custom Tag" ; "C" ;"^(%d) _KEYS" ;"%1 _KEYS" ;nil ;"FH5 FH6 GST" }) -- V1.3 -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 addRule({"Media Format" ;"Gedcom 5.5 FORM" ; "A" ;"^(%d) (FORM) (.*)";doObjFormat ;"5.5" ;"Std " }) -- V2.6 -- V4.0 Set level & value addRule({"Media Format" ;"Gedcom 5.5.1 FORM" ; "B" ;"^(%d) (FORM) (.*)";doObjFormat ;"5.5.1" ;"Str AGS GFT GSP HER MFT RFT TNG CEB " }) -- V2.6 TNG review? -- V2.8 Str Mdr -- V2.9 LFT removed -- V3.8 AGS RFT CEB -- V4.0 GFT GSP GST HER MFT addRule({"Media Format" ;"Remove from record" ; "C" ;"^(%d) FORM .*" ;doObjFormat ;nil ;"FTL FTM" }) -- V2.6 FTL/FTM addRule({"Media Format" ;"Keep Standard Tag" ; "D" ;"^(%d) FORM" ;"%1 FORM" ;nil ;"GST " }) -- V4.3 GST addRule({"Media Title" ;"Gedcom 5.5 TITL" ; "q" ;"^(%d) (TITL) (.*)";doObjFormat ;"5.5" ;"Std gst " }) -- V4.0 Set level -- V4.3 gst addRule({"Media Title" ;"Gedcom 5.5.1 TITL" ; "p" ;"^(%d) (TITL) (.*)";doObjFormat ;"5.5.1" ;"Str gst AGS FTL FTM GFT GSP HER MFT RFT TNG CEB" }) -- V2.8 TNG GSP FTL review 5.5.1? -- V2.9 LFT removed -- V3.8 AGS RFT CEB -- V4.0 FTM GFT GST HER MFT -- V4.3 gst addRule({"Media Sequence Id" ;"Remove entirely" ; "p" ;"^%d _SEQ .*" ; "" ;nil ;"Std fh5 fh6 gst" }) -- V4.0 like below but for _SEQ that replaces _ASID addRule({"Media Auto Seq Id" ;"Remove entirely" ; "p" ;"^%d _ASID .*" ; "" ;nil ;"Std fh5 fh6 gst" }) -- V1.3 %d Rule index " _ASID" -- V3.3 gst & fh5 -- V4.0 fh6 addRule({"Media Frame Area" ;"Remove entirely" ; "p" ;"^%d _AREA .*" ; "" ;nil ;"Std fh5 fh6 gst" }) -- V1.3 %d Rule index " _AREA" -- V3.3 gst & fh5 -- V4.0 fh6 addRule({"Media Exclude" ;"Remove entirely" ; "p" ;"^%d _EXCL .*" ; "" ;nil ;"Std fh5 fh6 gst" }) -- V1.3 %d Rule index " _EXCL" -- V3.3 gst & fh5 -- V4.0 fh6 addRule({"Media Caption" ;"Remove entirely" ; "p" ;"^%d _CAPT .*" ; "" ;nil ;"Std fh5 fh6 gst" }) -- V1.3 %d Rule index " _CAPT" -- V3.3 gst & fh5 -- V4.0 fh6 local arrAQP = {" _TYPE ";"PHOTO";"OTHER";} addRule({"Linked File" ;"Gedcom 5.5.1 FILE" ; "p" ;"^(1) _FILE (.+)" ;doFileLink ;nil ;"Std " }) -- V1.3 Std -- V3.8 nil -- V4.0 all changed from (.*) to (.+) to avoid missing filepath addRule({"Linked File" ;"Gedcom 5.5.1 FILE" ; "q" ;"^(1) _FILE (.+)" ;doFileLink ;"FindMyPast" ;"FMP " }) -- V3.3 FMP addRule({"Linked File" ;"Gedcom 5.5.1 FILE" ; "r" ;"^(1) _FILE (.+)" ;doFileLink ;arrAQP ;"AQP " }) -- V3.8 AQP addRule({"Linked File" ;"Check & Copy File" ; "p" ;"^(%d) FILE (.+)" ;doFileLink ;nil ;"Std " }) -- V1.3 Std Rule index " FILE" -- V3.8 nil addRule({"Linked File" ;"Check & Copy File" ; "q" ;"^(%d) FILE (.+)" ;doFileLink ;"FindMyPast" ;"FMP " }) -- V3.3 FMP Rule index " FILE" addRule({"Linked File" ;"Check & Copy File" ; "r" ;"^(%d) FILE (.+)" ;doFileLink ;arrAQP ;"AQP " }) -- V3.8 AQP Rule index " FILE" addRule({"Media Date" ;"Move to local Note" ; "A" ;"^%d _DATE .*" ;doLocDate ;"Media Date" ;"Std+" }) -- V1.3 doLocDate Rule index " _DATE" addRule({"Media Date" ;"Remove entirely" ; "D" ;"^%d _DATE .*" ; "" ;nil ;"Std- FTL FTM" }) -- V1.3 -- V1.8 FTL/FTM -- V2.8 Mdr addRule({"Media Date" ;"Move to Note Record" ; "B" ;"^(%d) _DATE .*" ;doObjNote ;"Media Date" ;" " }) -- V1.3 GFT disallows Local Object Notes -- V4.0 GFT removed addRule({"Media Date" ;"Keep Custom Tag" ; "C" ;"^(%d) _DATE" ;"%1 _DATE" ;nil ;"FH5 FH6 GST LFT" }) -- V1.3 -- V1.6 FH5 -- V2.9 LFT -- V3.1 GST -- V4.0 FH6 addRule({"Media Note" ;"Convert to _ASTDESC" ; "p" ;"^(%d) _ASTPERM 4" ;do_ASTPERM ;nil ;"FMP " }) -- V3.3 if IntFhVersion > 6 then addRule({"Source Template" ;"Move to Note Record" ; "A" ;"^1 _SRCT (.*)" ;doSrcTempLnk;"Metafield" ;"Std+" }) -- V4.0 Change all Source Template links to Note record links & _FIELD to Metafield labelled Notes addRule({"Source Template" ;"Remove entirely" ; "B" ;"^1 _SRCT (.*)" ;doSrcTempLnk;nil ;"Std-" }) -- V4.0 Remove all Source Template links addRule({"Source Template" ;"Keep Source Template" ; "C" ;"^1 _SRCT" ;"1 _SRCT" ;nil ;"GST+" }) -- V4.0 Retain all Source Template links addRule({"Source Template" ;"Reformat Metafields" ; "D" ;"^1 _SRCT (.*)" ;doSrcTempLnk;"Reformat" ;" " }) -- V4.2 Change the Source Template links to Note record links & reformat _FIELD Metafields addRule({"Source Template" ;"Handle Citation Field" ; "p" ;"^(%d) _FIELD .*" ;doSrcTempCit;"Metafield" ;"Std " }) -- V4.2 Convert Templated Source Citations according to above choice addRule({"Source Template" ;"Handle Source Template" ; "p" ;"^0 @(T%d+)@ .*" ;doSrcTempRec;"Source Template" ;"Std " }) -- V4.0 Handle all Source Template records according to above choice addRule({"Research Note" ;"Move to Note Record" ; "A" ;"^(%d) _RNOT (.*)" ;doResNoteLnk;"Note Record" ;"Std+" }) -- V4.0 Change all Research Note links to Note record links addRule({"Research Note" ;"Remove entirely" ; "B" ;"^(%d) _RNOT (.*)" ;doResNoteLnk;nil ;"Std-" }) -- V4.0 Remove all Research Note links addRule({"Research Note" ;"Keep Research Note" ; "C" ;"^(%d) _RNOT" ;"%1 _RNOT" ;nil ;"GST+" }) -- V4.0 Retain all Research Note links addRule({"Research Note" ;"Handle Research Note" ; "p" ;"^0 @(E%d+)@ .*" ;doResNoteRec;"Research Note" ;"Std " }) -- V4.0 Handle all Research Note records according to above choice end addRule({"Source Type" ;"Move to Record Note" ; "A" ;"^1 _TYPE (.*)" ;doRecNote ;"Source Type" ;"Std+" }) addRule({"Source Type" ;"Remove entirely" ; "C" ;"^1 _TYPE .*" ; "" ;nil ;"Std-" }) -- V2.8 Mdr addRule({"Source Type" ;"Keep Custom Tag" ; "B" ;"^1 _TYPE" ;"1 _TYPE" ;nil ;"FH5 FH6 GST" }) -- V1.6 FH5 -- V3.1 GST -- V4.0 FH6 addRule({"Source Type" ;"Change to 1 MEDI" ; "D" ;"^1 _TYPE" ;"1 MEDI" ;nil ;"LFT " }) -- V2.0 LFT addRule({"Source Type" ;"Change to 1 TYPE" ; "E" ;"^1 _TYPE" ;"1 TYPE" ;nil ;"HER " }) -- V3.8 HER~2019 addRule({"Short Title" ;"Move to Title or Note" ; "p" ;"^1 ABBR (.*)" ;doShortTitl ;"Short Title" ;"ANC FTA GFT MYH TPT" }) -- V1.9 HER -- V1.3 FTA GFT -- V2.2 ANC -- V3.2 MYH TPT -- V3.8 HER~2019 removed addRule({"Short Title" ;"Move to Title or Note" ; "q" ;"^1 ABBR (.*)" ;doShortTitl ;nil ;"FTL FTM" }) -- V1.8 FTL/FTM -- V2.0 addRule({"Publication Info" ;"Move to Record Note" ; "p" ;"^1 PUBL (.*)" ;doRecNote ;"Publication Info" ;"" }) -- V1.9 HER -- V3.8 HER~2019 removed addRule({"Text From Source" ;"Move to Record Note" ; "p" ;"^1 TEXT (.*)" ;doRecNote ;"Text From Source" ;"FTL FTM" }) -- V1.9 HER -- V1.8 FTL/FTM -- V3.8 HER~2019 removed addRule({"Source Citation" ;"Source Citation 5.5" ; "p" ;"^(%d) SOUR (.*)" ;doCite2Note ;"Cited Source" ;"Std fh5 fh6 gst tng " }) -- V4.0 Rule " SOUR" -- Move Note citation into its Note & keep Source citation link for 5.5 (and invalid Media link citation) -- V4.2 tng added addRule({"Source Citation" ;"Source Citation 5.5.1" ; "p" ;"^(%d) SOUR (.*)" ;doCite2Note ;"Cited Source" ;"Str AGS GFT MFT RFT CEB" }) -- V4.0 Rule " SOUR" -- Move Note citation into its Note & Place citation to a Note for 5.5.1 (and ditto ) -- V4.1 TNG removed addRule({"Source Citation" ;"Source Citation 5.5.1" ; "q" ;"^(%d) SOUR (.*)" ;doCite2Note ;"Copy Media" ;"FTM" }) -- V4.0 Rule " SOUR" -- Move Note citation into its Note & Place citation to a Note for 5.5.1 (and ditto ) -- V4.1 TNG removed -- V4.6 Copy Source Media & URL addRule({"Source Citation" ;"Adjust Citation" ; "p" ;"^(%d) SOUR (.*)" ;doCitation ;"Copy Media" ;"FTL " }) -- V1.8 Rule " SOUR" -- V1.9 FTL Record Citation & Copy Source Media & URL addRule({"Source Citation" ;"Adjust Citation" ; "q" ;"^(%d) SOUR (.*)" ;doCitation ;"Source Note" ;"ANC " }) -- V1.8 Rule " SOUR" -- V2.0 Heredis Record Citation & Source Note -- V2.2 ANC -- V3.8 HER~2019 removed addRule({"Source Citation" ;"Adjust Citation" ; "r" ;"^(%d) SOUR (.*)" ;doCitation ;" STORY" ;"FMP " }) -- V1.8 Rule " SOUR" -- V2.2 FMP addRule({"Source Note" ;"Make Source Record" ; "p" ;"^(%d) SOUR (.*)" ;doSourNote ;"Source Note" ;"MYH TPT" }) -- V1.3 Rule " SOUR" -- V2.4 RMT moved to doSources below -- V3.2 MYH TPT addRule({"Source Note/Cite" ;"Fix Source Note/Cite" ; "p" ;"^(%d) SOUR (.*)" ;doSources ;{NOTE=9;} ;"LFT " }) -- V2.0 LFT fails on Source Note and if Source on any Note and Source cannot be elevated addRule({"Source Note/Cite" ;"Fix Source Note/Cite" ; "q" ;"^(%d) SOUR (.*)" ;doSources ;{NOTE=4;_SHAR=2;} ;"RMT " }) -- V2.4 RMT fails on Source Note and if Source on any Note or Note record -- V4.0 was {NOTE=1;_SHAR=2;} addRule({"Source Note/Cite" ;"Fix Source Note/Cite" ; "r" ;"^(%d) SOUR (.*)" ;doSources ;{NOTE=4;} ;"AQP HER" }) -- V3.8 AQP HER fail on Source Note and if Source on any Note or Note record addRule({"Cited Entry Date" ;"Move to local Note" ; "p" ;"^(%d) DATA" ;doEntryDate ;"Citation Entry Date" ;"RMT " }) -- V4.0 RMT move Entry Date to local Note if IntFhVersion > 6 then addRule({"Assessments" ;"Convert to Standard" ; "p" ;"^(%d) _QUAY (.*)" ;doAssess ;nil ;"Std gst" }) -- V4.0 Convert FH V7 _QUAY Assessment to GEDCOM standard QUAY end addRule({"Submitter Language";"Remove entirely" ; "p" ;"^1 LANG .*" ; "" ;nil ;"GFT " }) -- V1.3 GFT addRule({"Updated" ;"Keep Optional Tag" ; "A" ;"^1 CHAN" ;"1 CHAN" ;nil ;"Std+ FH5 FH6" }) -- V4.0 FH6 addRule({"Updated" ;"Remove entirely" ; "B" ;"^1 CHAN" ;doUnnamed ;"ALL" ;"Std- fh5 fh6 ANC FTL FTM GSP LFT RMT RWW ZPG" }) -- V1.8 FTL/FTM -- V1.9 HER -- V2.0 GSP -- V2.2 ANC -- V2.4 RMT -- V2.6 LFT -- V2.8 Mdr -- V3.7 doUnnamed -- V4.0 fh6 RMT RWW ZPG addRule({"Updated" ;"Remove except INDI/FAM" ; "C" ;"^1 CHAN" ;doUnnamed ;"INDI, FAM" ;"AQP " }) -- V3.8 AQP HER addRule({"Updated" ;"Remove except INDI/SOUR"; "D" ;"^1 CHAN" ;doUnnamed ;"INDI, SOUR" ;"HER " }) -- V4.0 HER21 --[[ Array arrRoot is copied into array arrRule with dictionary named parameters as follows. Order of entries sets Rule number and sets the order of droplists on Extra Options tab. 1st column: Title for the droplists on Extra Options tab and Title for Result Set. 2nd column: Items for the droplists on Extra Options tab and Action for Result Set. 3rd column: Row letter to uniquely identify alternative items despite Rule changes. -- V2.0 Uppercase for GUI droplists. Lowercase a-n for GUI toggles. Lowercase o-z for hidden. 4th column: Pattern to match Old Tag and reduces to dicRule key in setPicked/useRules. Pattern minus ^ ( ) .* is added to droplist Title & ToolTip, and Mode added to Items. Key composed by removing 0 %d + %u ( ) .* to match GEDCOM tag or tag minus all digits. 5th column: Action to create New Tag and may be a string or function for useRules. 6th column: Arguments for many action functions may be a string or table. 7th column: Mode with optional +/- to select Rule for GEDCOM export mode in setRules. "Std" = All except named export modes in lower case. --]] local intLinkUsed = 0 -- Special usage count for Link to Note Record rule local intRuleUsed = 0 -- Max rule use GUI spin value local function setPicked(strTag,intPick,arrRule) -- Set the picked dictionary rule -- strTag ~ Level & Tag may be invoked once for "Std" and again for specific product mode to override "Std" -- intPick ~ Picked rule index -- arrRule ~ Rule parameters local strKey = strTag:gsub("[^1-4 _@%u]","") -- Exclude 0 %d + %u ( ) from Rule key if strKey == "2 _SHA" then -- e.g. "^(2) _SHA%u .*" => "2 _SHA ", "^0 @(P%d+)@ .*" => " @P@ ", "^(%d) PLAC (.*)" => " PLAC " -- V2.0 dicRule["2 _SHAR"] = arrRule dicRule["2 _SHAN"] = arrRule -- Special cases for "2 _SHA%u" else dicRule[strKey:match(strRuleKey)] = arrRule -- Rule key is optional level digit, a space, then tag/link -- V1.6 FH5 & FH6 end tblMode[strTag].Pick = intPick -- Rule currently picked if type(arrRule.New) == "string" and arrRule.New:match(arrRule.Old) then -- Remove rule if keep tag i.e. do nothing doDropRule({strTag}) end end -- local function setPicked local function setRules(oldMode) -- Create dictionaries of Rules and Modes for each tag -- V1.4 adds oldMode SetSharedData() -- V4.0 for intLabs, arrLabs in ipairs ( TblLabs ) do -- V3.3 local strName = arrLabs.Name local strTail = arrLabs.Tail or ":\t" local dicLabs = TblOption.Labs[strName] -- Ensure options exist -- V3.3 if not dicLabs then TblOption.Labs[strName] = { Value=strName..strTail; Where=StrAfter; } dicLabs = TblOption.Labs[strName] end dicLabel[strName] = dicLabs.Value -- Load dictionary of synthetic labels -- V3.3 dicWhere[strName] = dicLabs.Where -- Load dictionary of where they belong end isNotFull = ( not strObje:match("^FULL~") ) -- Conditions for Media processing rules above isLocObje = ( strObje:match("~LMO$") ) isAllType = ( strObje:match("^ALL~") ) isFileRel = ( strObje == "FILE~REL" ) isFileAbs = ( strObje == "FILE~ABS" ) -- V3.1 isWipeAll = ( strObje == "WIPE~ALL" ) isWipeLmo = ( isWipeAll or isLocObje ) -- Condition to remove Media records entirely isCaption = ( strObje == "FULL~ABS" or isFileRel or isFileAbs ) -- Condition to keep all Media record captions -- V3.3 dicRule = {} if not oldMode then -- Except when only resetting using oldMode -- V1.4 tblMode = {} -- Recreate dictionary of Modes for each tag tblMode.Drop = {} -- Array of GUI droplists tblMode.Togg = {} -- Array of GUI toggles end for intRule, arrRule in ipairs(arrRule) do -- Obtain dictionary text tag name allowing for level digit or %d initially local strTag = arrRule.Old:gsub("[%^%(%)]",""):match("^([^ ]+ [^ ]+)") -- Remove ^ ( ) then match leading digit or %d, a space, then tag/link -- V1.4 if not oldMode then -- Build entire tblMode if not tblMode[strTag] then tblMode[strTag] = {} -- Dictionary of Rule choices tblMode[strTag].Item = {} -- Items for GUI droplist tblMode[strTag].Row = {} -- Row letters to numbers & numbers to letters -- V2.0 tblMode[strTag].Rule = {} -- Rule numbers in arrRule tblMode[strTag].Pick = 0 -- Rule not picked yet end local strRow = arrRule.Row table.insert(tblMode[strTag].Item,arrRule.Item) -- Insert rule GUI droplist/toggle item table.insert(tblMode[strTag].Row,strRow) -- Insert rule letter in list of choices -- V2.0 local intRow = #tblMode[strTag].Row tblMode[strTag].Row[strRow:upper()] = intRow -- Insert rule letter reverse translations -- V2.0 tblMode[strTag].Row[strRow:lower()] = intRow -- Upper & lower case allows rules to migrate from toggle to droplist table.insert(tblMode[strTag].Rule,intRule) -- Insert rule number in list of choices if intRow == 2 and strRow:match("%u") then -- Upper-case row letters indicate GUI droplist items -- V2.0 table.insert(tblMode.Drop,strTag) -- Regular array of multi-choice tags in Rule order sets GUI droplist item order elseif strRow:match("[a-n]") then table.insert(tblMode.Togg,strTag) -- Regular array of single-choice tags in Rule order sets GUI toggles item order end tblMode[strTag].Title = arrRule.Title -- Save rule GUI label title local strRule = arrRule.Mode -- Check GEDCOM export mode local strAll = " Std%"..strFull.."? " -- V3.3 if ( strRule:match(strAll) and not strRule:match(strAbbr:lower()) ) -- Include rule for "Std" unless excluded by lower-case mode such as "fh5 ftl gft" or strRule:matches(strMode) or strRule:match(" "..strAbbr.." ") then -- Include rule if its GEDCOM mode matches current GEDCOM export mode setPicked(strTag,#tblMode[strTag].Rule,arrRule) -- Set the Rule currently picked end else -- Set the Rules based on oldMode -- V1.4 local intPick = oldMode[strTag].Pick if intRule == oldMode[strTag].Rule[intPick] then setPicked(strTag,intPick,arrRule) -- Set the Rule currently picked end end arrRule.Used = 0 -- Reset rule usage counts end intLinkUsed = 0 intRuleUsed = TblOption.Used -- Max rule use GUI spin value -- V1.4 strGedExport = dicRule["2 VERS"].Arg or "5.5" -- Set GEDCOM export version -- V4.0 needed in doAnalyse & useRules return tblMode end -- local function setRules local function useRules(arrRecord,arrLineNo) -- Use rules to convert input GEDCOM record tblRecord = arrRecord -- Array of text lines in GEDCOM record tblLineNo = arrLineNo -- Array of their import line numbers table.insert(tblRecord,"1 chan") -- Add dummy CHANge tag in case missing from record -- V2.2 table.insert(tblLineNo,0) strRec0 = arrRecord[1] -- Save record level 0 first line -- V3.7 -- V1.8 ptrRec0 = fhNewItemPtr() -- Pointer remains null for 0 HEAD -- V3.7 doConJoin(tblRecord,tblLineNo) -- V4.0 local intLine = 0 -- Current record line index repeat intLine = intLine + 1 local strText = doPrune(tblRecord[intLine]) -- Get next text line from record and prune "@@" and [[private text]] and rich text tblRecord[intLine] = strText -- Most programs do not honour GEDCOM "@@" convention local strRid, strTag = strText:match("^0 @%u(%d+)@ (_?%u+)") if strTag then if dicRule[" HEAD"] then -- Purge all expired Header dictionary Rules -- V1.4 -- V1.8 -- "1 SOUR" -- Essential to let Citation SOUR tag work -- V1.8 -- "1 FILE" -- Essential to allow Media FILE tag to work -- "2 _NOTE" & "3 _NOTE" -- Essential to enable Multimedia %d _NOTE tags doDropRule({" HEAD";"1 SOUR";"2 NAME";"1 FILE";"2 _VAR";"1 CHAR";"1 DEST";"1 COPR";"1 _UID";"1 SUBM";"1 _LIST";"2 _FLAG";"2 _NOTE";"3 _NOTE";"2 _IDS";"1 _ROOT";"1 _USED";"1 _PICT";}) -- V3.6 -- "1 _USED" -- V4.0 -- "1 _PICT" if IntFhVersion == 5 then -- Purge all FH V6 only Witness & Place Rules -- V2.0 doDropRule({"2 _SHAR";"2 _SHAN";"3 ROLE";" @P@";}) -- V2.3 removed " PLAC"; as needed for Place Tidy end end if strTag == "INDI" and dicName.RootId and intLine == 1 then if dicName.RootId == "I"..strRid then -- Return false to remove duplicate File Root Individual record -- V1.8 dicName.RootId = nil return false end elseif strTag == "FAM" and dicRule["1 _FLGS"] then -- Purge all expired Individual dictionary Rules -- V1.8 doDropRule({"1 NAME";"2 NICK";"2 NPFX";"2 NSFX";"2 GIVN";"2 SPFX";"2 SURN";"2 _USED";}) doDropRule({"1 ASSO";"2 RELA";"1 IDNO";"2 _PEDI";"1 _FLGS";}) elseif strTag == "OBJE" and isWipeLmo then -- Return false to remove Media record entirely for WIPE~ALL or ****~LMO -- V1.3 -- V1.4 -- V1.8 return false end intRecI = tonumber(strRid) -- V2.5 for doContNote() strRecT = strTag strRec0 = strText -- New record first line -- V3.7 -- V1.8 ptrRec0:MoveToRecordById(strTag,strRid) -- Make record pointer -- V3.7 end local strKey = strText:match(strRuleKey) or "" -- Rule key is level digit, a space, then tag or link local arrRule = dicRule[strKey] -- Lookup rule for text line key in dictionary or dicRule[strKey:gsub("%d","")] -- If no rule match then try key without digits e.g. "0 @P123@" => " @P@" if arrRule then -- Rule exists for this text key local strOld = arrRule.Old if strText:match(strOld) then -- Ensure a text tag match is found intOrig = tblLineNo[intLine] -- Imported line number -- V3.7 strOrig = strText -- Imported line text -- V3.7 local intRecd = #tblRecord tblRecord.Line = intLine -- Add line index to Record table tblRecord.Arg = arrRule.Arg -- Add rule argument to Record table tblRecord.Title= arrRule.Title -- Allow Title & Item to be temporarily altered -- V2.6 tblRecord.Item = arrRule.Item tblRecord.List = tblRecord.List -- Used by Named List & File Root conversion is NOTE or SOUR tblRecord.Pref = tblRecord.Pref -- Used by Named List line prefix conversion tblRecord.Text = { [0] = ""; } -- Array of text exported for original line -- V4.0 tblRecord.FILE = nil -- Position in array of text of FILE tag -- V4.0 strText = strText:gsub(strOld,arrRule.New) -- Get new text from string or action function if strOrig ~= strText or intRecd ~= #tblRecord -- Line or record length or text modified -- V4.0 or #tblRecord.Text > 0 then intLine = tblRecord.Line -- Cater for insertions & removals before current line -- V1.8 if #strText == 0 then -- V4.0 moved before Result Set update table.remove(tblRecord,intLine) -- Remove returned blank line -- V1.8 table.remove(tblLineNo,intLine) -- Remove the import line number to keep arrays in step intLine = intLine - 1 else tblRecord[intLine] = strText -- Save converted text line before insertion table.insert(tblRecord.Text,1,strText) -- V4.0 end local intUsed = arrRule.Used or intLinkUsed if intUsed <= intRuleUsed then -- Exclude heavily used rules -- V1.4 if intUsed == intRuleUsed then strOrig = " ... Rule used more than "..intRuleUsed.." times, so only some examples are listed. " -- V1.4 strNew = "" -- V4.0 tblRecord.Text = { } -- V4.0 end intUsed = intUsed + 1 -- Rule usage count if arrRule.Used then arrRule.Used = intUsed else intLinkUsed = intUsed end if #tblRecord.Text > 0 then table.insert(tblRecord.Text," ") -- V4.0 ensure trailing pilcrow end local strText = table.concat(tblRecord.Text,StrPilcrow):gsub("\r",StrPilcrow):gsub("\t"," ") -- Tidy exported text -- V4.0 -- V4.6 setResultSet(#ArrSort+1,arrRule.Rule,strOrig,strText) -- Update Result Set -- V4.5 end intGarbage = intGarbage + 1 if intGarbage > 10000 then -- Report memory usage occasionally -- V4.5 intGarbage = 0 doReportMemoryUsage() end end end elseif #strText == 0 or strText == "1 chan" then -- V2.3 table.remove(tblRecord,intLine) -- Remove dummy CHANge tag -- V2.2 table.remove(tblLineNo,intLine) -- Remove the import line number to keep arrays in step intLine = intLine - 1 end until intLine >= #tblRecord doDisJoin(tblRecord) -- V4.0 return ( #tblRecord > 0 ) -- Return true to export converted GEDCOM record, but false if empty record -- V1.8 end -- local function useRules local function setMode(strTag,intPick) -- Enable/disable dictionary rule for tag if intPick and intPick >= 1 and -- Ensure picked value is in range -- FH5 & FH6 intPick <= #tblMode[strTag].Rule then -- Enable picked dictionary rule setPicked(strTag,intPick,arrRule[tblMode[strTag].Rule[intPick]]) else dicRule[strTag] = nil -- Disable picked dictionary rule tblMode[strTag].Pick = 0 end end -- local function setMode return doAnalyse, setRules, useRules, setMode -- Return function methods end -- function MakeRules function GUI_MainDialogue() -- Graphical User Interface progbar.Setup() -- Popup in moddle progbar.Start("Preparing User Dialogue",16) -- Start Progress Bar while building GUI local function iupValue(strValue,tglA,tglB) -- Return toggle handle depending on toggle value -- V3.3 if strValue == "-" then return tglB else return tglA end end -- local function iupValue local function iupRadio(strValue,tglA,tglB) -- Return radio handle created from two toggles -- V3.3 return iup.radio { iup.hbox { Homogeneous="YES"; tglA; tglB; }; Value=iupValue(strValue,tglA,tglB); Expand="HORIZONTAL"; } end -- local function iupRadio TblOption.Mode = math.min(tonumber(TblOption.Mode) or 1,#TblMode) -- Ensure options are in range -- V1.8 TblOption.Char = math.min(tonumber(TblOption.Char) or 3,#TblChar) TblOption.Obje = math.min(tonumber(TblOption.Obje) or 1,#TblObje) TblOption.Priv = math.min(tonumber(TblOption.Priv) or 2,#TblPriv) -- V3.2 TblOption.Rich = math.min(tonumber(TblOption.Rich) or 1,#TblRich) -- V4.0 local strMode, strAbbr = StrGedcomExportMode() -- GEDCOM Export Mode = Std+, FH5+, FTM-, FMP-, etc, Abbr = Std, FH5, FTM, TNG, etc. -- V3.3 -- Create GUI controls local tglFull = iup.toggle{ Title="(+) Full Data "; } -- V3.3 local tglTrim = iup.toggle{ Title="(-) Brief Data "; } -- V3.3 local radMode = iupRadio ( TblOption[strAbbr].Full, tglFull, tglTrim ) -- V3.3 local lblGedcom = iup.label { Alignment="ARIGHT:ATOP"; Title="GEDCOM Export Mode:"; } -- V3.3 -- V4.0 local lstGedcom = iup.list { DropDown="YES"; Visible_Items="99"; } local boxGedcom = iup.hbox { Homogeneous="YES"; iup.hbox { radMode; lblGedcom; Margin="0x0"; }; lstGedcom; } -- V3.3 local lblNaming = iup.label { Alignment="ARIGHT:ATOP"; Title="Frame Naming:"; } local lstNaming = iup.list { DropDown="YES"; Visible_Items="99"; TblName[1]; TblName[2]; TblName[3]; } local boxNaming = iup.hbox { lblNaming; lstNaming; } local lblObject = iup.label { Alignment="ARIGHT:ATOP"; Title="Multimedia Conversion:"; } -- V3.3 -- V4.0 local lstObject = iup.list { DropDown="YES"; Visible_Items="99"; } local boxObject = iup.hbox { Homogeneous="YES"; iup.hbox { boxNaming; lblObject; Margin="0x0"; }; lstObject; } -- V4.0 local lblEncode = iup.label { Alignment="ARIGHT:ATOP"; Title="Character Set Encoding:"; } -- V3.3 -- V4.0 local lstEncode = iup.list { DropDown="YES"; Visible_Items="99"; } local boxEncode = iup.hbox { Homogeneous="YES"; lblEncode; lstEncode; } local btnBasics = iup.button{ Title="Reset these TBD Options"; } -- V4.0 local lblPrivate = iup.label { Alignment="ARIGHT:ATOP"; Title="[[private]] Text Option:"; } -- V3.2 -- V3.3 -- V4.0 local lstPrivate = iup.list { DropDown="YES"; Visible_Items="99"; } -- V3.2 local boxPrivate = iup.hbox { Homogeneous="YES"; iup.hbox { btnBasics; lblPrivate; Margin="0x0"; }; lstPrivate; } -- V4.0 local lblRichtxt = iup.label { Alignment="ARIGHT:ATOP"; Title="Rich Text Formatting:"; } -- V4.0 local lstRichtxt = iup.list { DropDown="YES"; Visible_Items="99"; } local boxRichtxt = iup.hbox { Homogeneous="YES"; lblRichtxt; lstRichtxt; } -- V4.0 local lblMaxHigh = iup.label { Alignment="ARIGHT:ATOP"; Title="Max height:"; } -- V4.0 local txtMaxHigh = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinAlign="RIGHT"; SpinInc=1; SpinMin=100; SpinMax=9999; } -- V3.7 local lblMaxWide = iup.label { Alignment="ARIGHT:ATOP"; Title="Max width:"; } -- V4.0 local txtMaxWide = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinAlign="RIGHT"; SpinInc=1; SpinMin=100; SpinMax=9999; } -- V3.7 local lblMaxUsed = iup.label { Alignment="ARIGHT:ATOP"; Title="Max rule use:"; } -- V4.0 local txtMaxUsed = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinAlign="RIGHT"; SpinInc=1; SpinMin=3 ; SpinMax=999; } local boxMaxSize = iup.hbox { Homogeneous="YES"; iup.label{}; lblMaxUsed; txtMaxUsed; iup.label{}; lblMaxHigh; txtMaxHigh; lblMaxWide; txtMaxWide; } local tglFrames = iup.toggle{ Title=" Use JPEG file format for all exported image files"; } -- V3.7 local tglFullOn = iup.toggle{ Title=" Apply the settings above to all image files"; } -- V3.7 local btnChoose = iup.button{ Title="Choose Export Folder"; } local btnDelete = iup.button{ Title="Empty Export Folder"; } local boxSelect = iup.hbox { Homogeneous="YES"; iup.hbox { btnChoose; btnDelete; }; iup.vbox { Gap="0"; tglFrames; tglFullOn; }; Gap="20"; Margin="20x2"; } -- V3.7 local lblFolder = iup.label { Alignment="ARIGHT"; Title=TblOption.Path; } local tglFolder = iup.toggle{ Title=" Keep Media folders"; } -- V2.9 local boxFolder = iup.hbox { lblFolder; Gap="20"; tglFolder; } -- V2.9 local boxShared = iup.vbox { boxMaxSize; boxSelect; boxFolder; Margin="2x2"; } -- V2.9 local frmShared = iup.frame { Title=" Shared Settings "; boxShared; } -- V1.8 local btnExport = iup.button{ Title="Click here to export GEDCOM && Media files in chosen format..."; } local boxExport = iup.hbox { Homogeneous="NO"; iup.label{Expand="YES"}; btnExport; iup.label{Expand="YES"}; Margin="2x20"; } local tglTidying = iup.toggle{ Title=" Tidy Places && Addresses"; } -- V3.3 local tglFactDef = iup.toggle{ Title=" Export Fact Definitions"; } -- V3.3 local tglPrefix = iup.toggle{ Title=" Insert Synthetic Prefix"; } -- V3.3 local tglCaption = iup.toggle{ Title=" Avoid Repeat Captions"; } -- V3.3 local btnExtras = iup.button{ Title="Reset these TBD Options"; } local btnOthers = iup.button{ Title="Reset these TBD Options"; } local btnLabelA = iup.button{ Title="Reset all the Label Options"; } -- V3.3 -- V4.0 local btnLabelB = iup.button{ Title="Reset all the Label Options"; } -- V3.3 -- V4.0 local btnDefault = iup.button{ Title="Restore Defaults"; } local btnSetFont = iup.button{ Title="Set Window Fonts"; } local btnGetHelp = iup.button{ Title=" Help && Advice"; } local btnDestroy = iup.button{ Title="Close Plugin"; } local boxButtons = iup.hbox { Homogeneous="YES"; btnDefault; btnSetFont; btnGetHelp; btnDestroy; } local boxBasic = iup.vbox { boxGedcom; boxObject; boxEncode; boxPrivate; frmShared; boxExport; } -- V3.2 if IntFhVersion > 6 then iup.Insert( boxBasic, frmShared, boxRichtxt ) end -- v4.0 local boxExtra = iup.vbox { Gap="2"; Margin="1x1"; } -- Populated in doPopulateDropLists() below local boxOther = iup.vbox { Gap="2"; Margin="1x1"; } -- Populated in doPopulateToggles() below local boxLab_A = iup.vbox { Gap="2"; Margin="1x1"; } -- Populated in doPopulateLabels() below local boxLab_B = iup.vbox { Gap="2"; Margin="1x1"; } -- Populated in doPopulateLabels() below local tabControl = iup.tabs { -- Create the Tab controls layout boxBasic; TabTitle0=" Basic Options "; boxExtra; TabTitle1=" Extra Options "; boxOther; TabTitle2=" Other Options "; boxLab_A; TabTitle3=" Labels Set A "; -- V4.0 -- V3.3 boxLab_B; TabTitle4=" Labels Set B "; -- V4.0 } if fhGetAppVersion() > 6 then -- FH V7 IUP 3.28 tabControl.TabPadding = "8x4" else -- FH V6 IUP 3.11 tabControl.Padding = "8x4" end -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local dialogMain = iup.dialog { Title=iup_gui.Plugin..iup_gui.Version; iup.vbox { Gap="4"; Margin="4x4"; tabControl; boxButtons; }; } local doAnalyse, setRules, useRules, setMode = MakeRules() -- Prototype for conversion Rules returns function methods local tblMode = {} -- Dictionary & array of rules per text tag for chosen Mode of GEDCOM Export local iupDrop = {} -- Dictionary of Extra Options droplist handles per text tag local iupTogg = {} -- Dictionary of Other Options toggle handles per text tag local iupLabs = {} -- Dictionary of Label Options text & droplist handles per text label progbar.Step(1) local function doSaveSettings() for intTab, tblTab in ipairs ({tblMode.Drop;tblMode.Togg}) do -- For the Extra & Other Options tabs for intTag, strTag in ipairs ( tblTab ) do -- Get the Options for droplist/toggle choice Rules -- V2.0 local intPick = tblMode[strTag].Pick -- Convert integer choice to a letter or zero -- V2.0 TblOption[strMode][strTag] = tblMode[strTag].Row[intPick] or 0 end end SaveSettings() -- Save sticky data settings end -- local function doSaveSettings() local function doUpdateFolder() -- Update and check export folder controls -- V2.8 lblFolder.Title = TblOption.Path if IsIupUtf8 then lblFolder.Title = encoder.StrANSI_UTF8(TblOption.Path) -- V1.6 end if general.FlgFolderExists(TblOption.Path) then btnExport.Active = "YES" lblFolder.FgColor = iup_gui.Body else btnExport.Active = "NO" lblFolder.FgColor = iup_gui.Risk end end -- local function doUpdateFolder() local function setFullMode(strAbbr) -- Set Full v Brief radio toggles -- V3.3 tglFull.Tip = tglFull.Tip tglTrim.Tip = tglTrim.Tip -- Refresh XP Tooltips TblOption[strAbbr] = { } if tglFull.Value == "ON" then TblOption[strAbbr].Full = "+" else TblOption[strAbbr].Full = "-" end local strMode = StrGedcomExportMode() if not TblOption[strMode] then ResetGedcomOptions(TblMode[TblOption.Mode],TblOption[strAbbr].Full) end end -- local function setFullMode() local function setControls() -- Reset GUI control values TblOption.Mode = math.min(TblOption.Mode,#TblMode) -- Ensure options are in range -- V1.8 strMode, strAbbr = StrGedcomExportMode() -- V3.3 TblOption.Obje = math.min(TblOption[strMode].Obje,#TblObje) -- Set Media Object option for current Mode of GEDCOM Export -- V1.8 TblOption.Char = math.min(TblOption[strMode].Char,#TblChar) -- Set Character Encoding for current Mode of GEDCOM Export -- V1.8 TblOption.Priv = math.min(TblOption[strMode].Priv,#TblPriv) -- Set [[private]] text option for current Mode of GEDCOM Export -- V3.2 TblOption.Rich = math.min(TblOption[strMode].Rich,#TblRich) -- Set rich text format option for current Mode of GEDCOM Export -- V4.0 radMode.Value = iupValue(TblOption[strAbbr].Full,tglFull,tglTrim) -- V3.3 setFullMode(strAbbr) -- V3.3 btnBasics.Title = "Reset these "..strMode.." Options" -- Update reset button Title for current GEDCOM export mode -- V4.0 lstGedcom.Value = TblOption.Mode -- Set the Basic Options lstNaming.Value = TblOption.Name lstObject.Value = TblOption.Obje lstEncode.Value = TblOption.Char lstPrivate.Value= TblOption.Priv -- V3.2 lstRichtxt.Value= TblOption.Rich -- V4.0 tglFrames.Value = TblOption.Jpeg -- V1.4 tglFullOn.Value = TblOption.Full -- V3.7 tglFolder.Value = TblOption.Keep -- V2.9 doUpdateFolder() -- V2.8 txtMaxHigh.SpinValue = TblOption.High -- Including spin values txtMaxWide.SpinValue = TblOption.Wide txtMaxUsed.SpinValue = TblOption.Used local strObje = TblObje[TblOption.Obje].Abbr if strObje:match("^PART~") -- Maximum height/width pixel values & JPEG toggle only apply to Part Frames -- V1.8 or strObje:match("^ALL~") or TblOption.Full == "ON" then -- Unless all files are included -- V3.7 lblMaxHigh.Active = "YES" txtMaxHigh.Active = "YES" lblMaxWide.Active = "YES" txtMaxWide.Active = "YES" tglFrames .Active = "YES" else lblMaxHigh.Active = "NO" txtMaxHigh.Active = "NO" lblMaxWide.Active = "NO" txtMaxWide.Active = "NO" tglFrames .Active = "NO" end tglTidying.Value = TblOption[strMode].Tidy -- V3.3 tglFactDef.Value = TblOption[strMode].Fact -- V3.3 tglPrefix .Value = TblOption[strMode].Pref -- V3.3 tglCaption.Value = TblOption[strMode].Capt -- V3.3 for intTab, tblTab in ipairs ({tblMode.Drop;tblMode.Togg}) do -- For the Extra & Other Options tabs for intTag, strTag in ipairs ( tblTab ) do -- Set the Options for droplist/toggle choice Rules -- V2.0 local intPick = TblOption[strMode][strTag] or tblMode[strTag].Pick if type(intPick) == "string" then -- V2.0 Row letters intPick = tblMode[strTag].Row[intPick] or tblMode[strTag].Pick end intPick = math.min(intPick,#tblMode[strTag].Rule) -- Ensure choice is in range -- V1.8 TblOption[strMode][strTag] = intPick -- Enable the picked Rule setMode(strTag,intPick) if intTab == 1 then iupDrop[strTag].Value = intPick -- Display picked droplist Rule elseif iupTogg[strTag] then if intPick ~= 1 then intPick = 2 end local arrState = {"ON";"OFF"} iupTogg[strTag].Value = arrState[intPick] -- Adjust picked toggle Rule end end end for intLabs, arrLabs in ipairs ( TblLabs ) do -- For the Label Options set the text and droplist values -- V3.3 local strName = arrLabs.Name local strTail = arrLabs.Tail or ":\t" local dicLabs = TblOption.Labs[strName] if dicLabs then -- Remove tail from label for text value iupLabs[strName].Value.Value = dicLabs.Value:gsub(strTail.."$","") iupLabs[strName].Where.Value = dicLabs.Where end end doSaveSettings() -- Save sticky data settings end -- local function setControls -- Set other GUI control attributes local tblControls={{"Font"; "FgColor"; "Padding"; "Tip"; {"TipBalloon";"Balloon"}; {"Expand";"YES"}; {"help_cb";function() iup_gui.HelpDialogue(IntTabPosn) end}; setControls; }; [dialogMain]= { "FontHead"; "Head"; }; [tabControl]= { "FontHead"; "Head"; "8x4"; "Select export basic, extra, other, or label options"; }; [tglFull] = { "FontHead"; "Safe"; "0x0"; "Comprehensive Project GEDCOM details"; }; -- V3.3 [tglTrim] = { "FontHead"; "Safe"; "0x0"; "Abbreviated Project GEDCOM details"; }; -- V3.3 [lblGedcom] = { "FontHead"; "Body"; "4x2"; "Choose the Mode of GEDCOM export rules for target program"; }; [lstGedcom] = { "FontBody"; "Safe"; "0x0"; "Choose the Mode of GEDCOM export rules for target program"; }; [lblNaming] = { "FontHead"; "Body"; "4x2"; "Choose the part-frame naming format"; }; [lstNaming] = { "FontBody"; "Safe"; "0x0"; "Choose the part-frame naming format"; }; [lblObject] = { "FontHead"; "Body"; "4x2"; "Choose Multimedia Object file conversion option for current Mode"; }; [lstObject] = { "FontBody"; "Safe"; "0x0"; "Choose Multimedia Object file conversion option for current Mode"; }; [lblEncode] = { "FontHead"; "Body"; "4x2"; "Choose GEDCOM character encoding standard for current Mode"; }; [lstEncode] = { "FontBody"; "Safe"; "0x0"; "Choose GEDCOM character encoding standard for current Mode"; }; [btnBasics] = { "FontBody"; "Safe"; "0x0"; "Reset these options for current Mode of GEDCOM export"; }; -- V4.0 [lblPrivate]= { "FontHead"; "Body"; "4x2"; "Choose whether to exclude [[private]] text for current Mode"; }; -- V3.2 [lstPrivate]= { "FontBody"; "Safe"; "0x0"; "Choose whether to exclude [[private]] text for current Mode"; }; -- V3.2 [lblRichtxt]= { "FontHead"; "Body"; "4x2"; "Choose how to handle rich text formatting for current Mode"; }; -- V4.0 [lstRichtxt]= { "FontBody"; "Safe"; "0x0"; "Choose how to handle rich text formatting for current Mode"; }; -- V4.0 [frmShared] = { "FontHead"; "Body"; false; }; [lblMaxHigh]= { "FontBody"; "Body"; "0x0"; "Choose Multimedia Object file maximum pixel height for files for all Modes"; }; [txtMaxHigh]= { "FontBody"; "Safe"; "0x0"; "Choose Multimedia Object file maximum pixel height for files for all Modes"; }; [lblMaxWide]= { "FontBody"; "Body"; "0x0"; "Choose Multimedia Object file maximum pixel width for files for all Modes"; }; [txtMaxWide]= { "FontBody"; "Safe"; "0x0"; "Choose Multimedia Object file maximum pixel width for files for all Modes"; }; [lblMaxUsed]= { "FontBody"; "Body"; "0x0"; "Set usage limit for same Rule listed in Result Set for all Modes"; }; [txtMaxUsed]= { "FontBody"; "Safe"; "0x0"; "Set usage limit for same Rule listed in Result Set for all Modes"; }; [tglFrames] = { "FontBody"; "Safe"; "0x0"; "Use JPEG format where possible for all image files for all Modes"; }; -- V1.4 -- V3.7 [tglFullOn] = { "FontBody"; "Safe"; "0x0"; "Choose to include all image files in these settings for all Modes,\n otherwise only Part frame files are included"; }; -- V3.7 [btnChoose] = { "FontBody"; "Safe"; "1x9"; "Choose GEDCOM and Media files export folder path for all Modes"; }; -- V4.0 was "1x1" [btnDelete] = { "FontBody"; "Risk"; "1x9"; "Empty GEDCOM and Media files from export folder for all Modes"; }; -- V4.0 was "1x1" [lblFolder] = { "FontHead"; "Body"; "0x0"; "Current GEDCOM and Media files export folder path for all Modes"; }; [tglFolder] = { "FontBody"; "Safe"; "0x0"; "Choose whether to keep the Media subfolders for all Modes"; }; -- V2.9 [btnExport] = { "FontHead"; "Safe"; "0x8"; "Start the GEDCOM and Media files export process"; }; [tglTidying]= { "FontBody"; "Safe"; "0x0"; "Choose whether to tidy all Place and Address fields"; }; -- V3.3 [tglFactDef]= { "FontBody"; "Safe"; "0x0"; "Choose whether to export all Fact Set Definitions"; }; -- V3.3 [tglPrefix] = { "FontBody"; "Safe"; "0x0"; "Choose whether to insert prefix on synthetic items"; }; -- V3.3 [tglCaption]= { "FontBody"; "Safe"; "0x0"; "Choose whether to avoid repeated Caption Notes"; }; -- V3.3 [btnExtras] = { "FontBody"; "Safe"; "0x0"; "Reset these options for current Mode of GEDCOM export"; }; [btnOthers] = { "FontBody"; "Safe"; "0x0"; "Reset these options for current Mode of GEDCOM export"; }; [btnLabelA] = { "FontBody"; "Safe"; "0x0"; "Reset these options for all Modes of GEDCOM export"; }; -- V3.3 [btnLabelB] = { "FontBody"; "Safe"; "0x0"; "Reset these options for all Modes of GEDCOM export"; }; -- V3.3 [btnDefault]= { "FontBody"; "Safe"; "1x1"; "Restore all option settings, and window positions and sizes"; }; [btnSetFont]= { "FontBody"; "Safe"; "1x1"; "Alter the window interface font styles"; }; [btnGetHelp]= { "FontBody"; "Safe"; "1x1"; "Access the online Help and Advice pages"; }; [btnDestroy]= { "FontBody"; "Risk"; "1x1"; "Close Plugin with Result Set of Rules used"; }; } local function setControlsActive(strMode) -- Enable/Disable controls during busy actions tabControl.Active = strMode lblMaxHigh.Active = strMode txtMaxHigh.Active = strMode lblMaxWide.Active = strMode txtMaxWide.Active = strMode tglFrames .Active = strMode -- V 3.7 boxButtons.Active = strMode end -- local function setControlsActive local function setToolTip(iupItem) -- Refresh control tooltip otherwise it vanishes in XP! iupItem.Tip = iupItem.Tip end -- local function setToolTip local function iupHbox(iupLeft,iupRight,strTitle) -- Make homogeneous iup.hbox from two control handles local iupBox = iup.hbox { Homogeneous="YES"; Margin="0x0"; iupLeft; iupRight; } if strTitle and IntFhVersion == 5 and ( strTitle:match("^Place") or strTitle:match("^Witness") ) then iupBox.Active = "NO" -- Exclude FH V6.0 Place Record/Field and Witness Role rules from FH V5.0 end return iupBox end -- local function iupHbox local function doPopulateDropLists() -- Populate all the GUI droplists with item choices for intMode, tblMode in ipairs ( TblMode ) do lstGedcom[intMode] = " ("..tblMode.Abbr..") "..tblMode.Full -- Populate the Basic Options Mode of GEDCOM Export droplist end for intChar, tblChar in ipairs ( TblChar ) do lstEncode[intChar] = " ("..tblChar.Abbr..") "..tblChar.Full -- Populate the Basic Options Character Encoding droplist end for intObje, tblObje in ipairs ( TblObje ) do lstObject[intObje] = " ("..tblObje.Abbr..") "..tblObje.Full -- Populate the Basic Options Multimedia Object droplist end for intPriv, tblPriv in ipairs ( TblPriv ) do lstPrivate[intPriv] = " ("..tblPriv.Abbr..") "..tblPriv.Full-- Populate the Basic Options [private]] text droplist -- V3.2 end for intRich, tblRich in ipairs ( TblRich ) do lstRichtxt[intRich] = " ("..tblRich.Abbr..") "..tblRich.Full-- Populate the Basic Options rich text fotmat droplist -- V4.0 end tblMode = setRules() -- Update the dictionary of Rules for GEDCOM mode local boxDrop, boxLeft, boxRight for intTag, strTag in ipairs ( tblMode.Drop ) do -- Create the Extra Options GUI for multi-choice Rules local tblMode = tblMode[strTag] local lblDrop = iup.label { Title=tblMode.Title.." "..strTag..":"; Alignment="ARIGHT"; } local lstDrop = iup.list { DropDown="YES"; Visible_Items="9"; VisibleColumns="9"; } tblControls[lblDrop] = { "FontBody"; "Body"; "4x2"; "Choose alternative rule for "..strTag; } tblControls[lstDrop] = { "FontBody"; "Safe"; "0x0"; "Choose alternative rule for "..strTag; } lstDrop.action = function(self,strText,intItem,intState) -- Action for Extra Options dropdown lists if intState == 1 then TblOption[strMode][strTag] = intItem -- Pick alternative choice of rule setMode(strTag,intItem) setToolTip(self) end end -- function lstDrop:action iupDrop[strTag] = lstDrop -- Save DropDown iup.list control handle for intItem, strItem in ipairs ( tblMode.Item ) do lstDrop[intItem] = strItem -- Populate the Extra Options dropdown list Items end boxDrop = iupHbox(lblDrop,lstDrop,tblMode.Title) -- Create option box from label and droplist if boxLeft then boxRight = boxDrop iup.Append(boxExtra,iupHbox(boxLeft,boxRight)) -- Create the option boxes side by side in pairs boxLeft = nil else boxLeft = boxDrop end end if boxLeft then iup.Append(boxExtra,iupHbox(boxLeft,iup.hbox{})) -- Create remaining odd left option box with blank on right -- V3.3 end iup.Append(boxExtra,iup.hbox{Homogeneous="YES";tglTidying;tglFactDef;tglPrefix;tglCaption;btnExtras}) -- Append the Tidying, FactDef, Prefix, Caption toggles and reset button -- V3.3 end -- local function doPopulateDropLists local function doPopulateToggles() -- Populate all the GUI toggles -- V2.0 local boxTogg, boxLeft, boxRight for intTag, strTag in ipairs ( tblMode.Togg ) do -- Create the Other Options GUI for toggle Rules local tblMode = tblMode[strTag] local lblTogg = iup.label { Title=tblMode.Title.." "..strTag..":"; Alignment="ARIGHT:ACENTER"; } local tglTogg = iup.toggle{ Title=tblMode.Item[1]:gsub("%(.+%)",""); } tblControls[lblTogg] = { "FontBody"; "Body"; "4x1"; "Toggle rule for "..strTag; } tblControls[tglTogg] = { "FontBody"; "Safe"; "4x1"; "Toggle rule for "..strTag; } tglTogg.action = function(self,intState) -- Action for Other Options toggles TblOption[strMode][strTag] = intState -- Pick alternative choice of rule setMode(strTag,intState) setToolTip(self) end -- function tglTogg:action iupTogg[strTag] = tglTogg -- Save Toggle iup.toggle control handle boxTogg = iupHbox(lblTogg,tglTogg,tblMode.Title) -- Create option box from toggle and label if boxLeft then boxRight = boxTogg iup.Append(boxOther,iupHbox(boxLeft,boxRight)) -- Create the option boxes side by side in pairs boxLeft = nil else boxLeft = boxTogg end end boxRight = iupHbox(iup.hbox{},btnOthers) iup.Append(boxOther,iupHbox(boxLeft or iup.hbox{},boxRight)) -- Append reset button after last/dummy box end -- local function doPopulateToggles local function doPopulateLabels() -- Populate all the GUI synthetic labels -- V3.3 local boxLabs, boxLeft, boxRight local boxLabel = boxLab_A -- Select Labels Set A tab -- V4.0 local intHalf = math.floor( #TblLabs / 4 ) * 2 for intLabs, arrLabs in ipairs ( TblLabs ) do local strName = arrLabs.Name local strHelp = arrLabs.Help or strName -- V4.0 local strTail = arrLabs.Tail or ":\t" local dicLabs = TblOption.Labs[strName] -- Ensure options exist if not dicLabs then TblOption.Labs[strName] = { Value=strName..strTail; Where=StrAfter; } dicLabs = TblOption.Labs[strName] end local strValue = dicLabs.Value:gsub(strTail.."$","") -- Strip the tail local strWhere = dicLabs.Where local lblLabel = iup.label { Title=strName..":"; Alignment="ARIGHT"; } local txtValue = iup.text { Value=strValue; } local lstWhere = iup.list { DropDown="YES"; "Before other notes"; "After other notes"; } tblControls[lblLabel] = { "FontBody"; "Body"; "0x0"; "Choose alternative text for "..strHelp; } -- V4.0 tblControls[txtValue] = { "FontBody"; "Safe"; "0x0"; "Choose alternative text for "..strName; } tblControls[lstWhere] = { "FontBody"; "Safe"; "0x0"; "Choose alternative position for "..strName; } txtValue.action = function(self,strChar,strValue) -- Action for Label Options text values if #strValue > 0 then strValue = strValue..strTail end TblOption.Labs[strName].Value = strValue setToolTip(self) end -- function txtValue:action lstWhere.action = function(self,strText,intItem,intState) -- Action for Label Options dropdown lists if intState == 1 then TblOption.Labs[strName].Where = intItem setToolTip(self) end end -- function lstWhere:action iupLabs[strName] = { Value=txtValue; Where=lstWhere; } -- Save Text iup.text & Droplist iup.list control handles local iupWhere = lstWhere if arrLabs.Tail then iupWhere = iup.label{} end -- Inhibit where droplist if special Tail exists boxLabs = iup.hbox{Homogeneous="YES";lblLabel;txtValue;iupWhere;} -- Create option box from label, text & droplist if boxLeft then boxRight = boxLabs iup.Append(boxLabel,iupHbox(boxLeft,boxRight)) -- Create the option boxes side by side in pairs boxLeft = nil else boxLeft = boxLabs end if intLabs == intHalf then boxRight = iupHbox(iup.hbox{},btnLabelA) -- Append reset button after last/dummy box on Label Set A tab -- V4.0 iup.Append(boxLabel,iupHbox(boxLeft or iup.hbox{},boxRight)) boxLeft = nil boxLabel = boxLab_B -- Select Labels Set B tab -- V4.0 end end boxRight = iupHbox(iup.hbox{},btnLabelB) iup.Append(boxLabel,iupHbox(boxLeft or iup.hbox{},boxRight)) -- Append reset button after last/dummy box on Label Set B tab end -- local function doPopulateLabels local function getSpinValues() -- Update the spin control values TblOption.High = tonumber(txtMaxHigh.SpinValue) TblOption.Wide = tonumber(txtMaxWide.SpinValue) TblOption.Used = tonumber(txtMaxUsed.SpinValue) end -- local function getSpinValues function tglFull:action(intState) -- Action for Full v Brief radio toggle -- V3.3 setControlsActive("NO") setFullMode(strAbbr) tblMode = setRules() -- Set default dictionary of Rules for chosen Full v Trim mode getSpinValues() setControls() -- Reset controls setControlsActive("YES") end -- function tglFull:action function lstGedcom:action(strText,intItem,intState) -- Action for choose GEDCOM dropdown if intState == 1 then setControlsActive("NO") TblOption.Mode = intItem -- Save chosen Mode of GEDCOM Export strMode, strAbbr = StrGedcomExportMode() -- V3.3 tblMode = setRules() -- Set default dictionary of Rules for chosen GEDCOM mode getSpinValues() setControls() -- Reset controls setToolTip(lstGedcom) setControlsActive("YES") end end -- function lstGedcom:action function lstEncode:action(strText,intItem,intState) -- Action for choose Encode dropdown if intState == 1 then setControlsActive("NO") for intChar, tblChar in ipairs ( TblChar ) do if tblChar.Mode:match(strMode:lower()) -- Lowercase mode identifies an inhibited character encoding -- V1.4 and strText:match(tblChar.Abbr) then lstEncode.Value = TblOption.Char -- e.g. For "TNG" inhibit "ANSI" -- V1.5 OK with IUP 3.11.2 setToolTip(lstEncode) return iup.IGNORE -- Ignore the choice end end TblOption.Char = intItem -- Otherwise, save chosen character encoding TblOption[strMode].Char = TblOption.Char getSpinValues() setControls() -- Reset controls setToolTip(lstEncode) setControlsActive("YES") end end -- function lstEncode:action function lstNaming:action(strText,intItem,intState) -- Action for choose Naming dropdown if intState == 1 then setControlsActive("NO") TblOption.Name = intItem -- Save chosen Frame Naming mode getSpinValues() setControls() -- Reset controls setToolTip(lstNaming) setControlsActive("YES") end end -- function lstNaming:action function lstObject:action(strText,intItem,intState) -- Action for choose Media dropdown if intState == 1 then setControlsActive("NO") TblOption.Obje = intItem -- Save chosen Media Object mode TblOption[strMode].Obje = TblOption.Obje getSpinValues() setControls() -- Reset controls setToolTip(lstObject) setControlsActive("YES") end end -- function lstObject:action function lstPrivate:action(strText,intItem,intState) -- Action for choose [[private]] dropdown -- V3.2 if intState == 1 then setControlsActive("NO") TblOption.Priv = intItem -- Save chosen Private option mode TblOption[strMode].Priv = TblOption.Priv getSpinValues() setControls() -- Reset controls setToolTip(lstPrivate) setControlsActive("YES") end end -- function lstPrivate:action function lstRichtxt:action(strText,intItem,intState) -- Action for choose rich text dropdown -- V4.0 if intState == 1 then setControlsActive("NO") TblOption.Rich = intItem -- Save chosen Rich Text option mode TblOption[strMode].Rich = TblOption.Rich getSpinValues() setControls() -- Reset controls setToolTip(lstRichtxt) setControlsActive("YES") end end -- function lstRichtxt:action function tglFrames:action(intState) -- Action for frames JPEG toggle -- V1.4 setControlsActive("NO") TblOption.Jpeg = tglFrames.Value doSaveSettings() setToolTip(tglFrames) setControlsActive("YES") end -- function tglFrames:action function tglFullOn:action(intState) -- Action for Full frames toggle -- V3.7 setControlsActive("NO") TblOption.Full = tglFullOn.Value doSaveSettings() setControls() setToolTip(tglFullOn) setControlsActive("YES") end -- function tglFullOn:action function btnChoose:action() -- Action for Choose Export Folder button setControlsActive("NO") local tblMain = iup_gui.DialogueAttributes("Main") -- Obtain position of Main dialogue local intMainX = tblMain.CoordX local intMainY = tblMain.CoordY local strPath = TblOption.Path -- Create Export Folder Path Dialogue local strDirectory = strPath if not general.FlgFolderExists(strPath) then strDirectory = iup_gui.PublicPath end local dialogPath = iup.filedlg{ DialogType="DIR"; Title="Please choose the Export Folder Path"; Directory=strDirectory; } dialogPath:popup( intMainX+100, intMainY ) -- Popup in same place as Main dialogue if dialogPath.Status ~= "-1" then strPath = dialogPath.Value if (strPath.."\\"):match("%.fh_data\\") then -- Disallow any Project data folder -- V2.9 iup_gui.MemoDialogue("Cannot choose any Project data or Media folder:\n"..strPath.."\n") else TblOption.Path = strPath -- Update chosen export folder path doUpdateFolder() -- V2.8 iup_gui.ShowDialogue("Main") doSaveSettings() end end setControlsActive("YES") end -- function btnChoose:action local function isExportGedcom(strFile) -- Return true if matching export Gedcom file for intMode, tblMode in ipairs ( TblMode ) do -- Modes of Gedcom Export local strMode = tblMode.Abbr for intChar, tblChar in ipairs ( TblChar ) do -- Character Encodings local strCode = tblChar.Abbr local strPath = TblOption.Path.."\\"..StrFile.." "..strMode.." "..strCode..".ged" -- V2.9 if strPath == strFile then return true end -- Valid match found end end return false end -- local function isExportGedcom local function doEmptyFolder(strFolder) -- Empty each export folder recursively -- V2.9 local strExport = TblOption.Path.."\\Media\\" for strFile, tblAttr in general.DirTree(strFolder) do local strPath, strName, strType = general.SplitFilename(strFile) if (strPath.."Media\\"):matches(strExport) -- Ensure only export folders are emptied or strPath:match("\\%[%d+%]\\$") -- Ensure only export folders are emptied -- V4.0 or strPath == StrZipFMP then -- V3.3 if tblAttr.mode == "file" and strFile ~= StrImport then -- Ensure import Gedcom file is avoided if strName:match("^%d+O%d+ .+") or strName:match("^.+ %[%d+%]%..-$") or strPath:match("\\%[%d+%]\\$") -- V4.0 or isExportGedcom(strFile) or strPath == StrZipFMP then -- V3.3 general.DeleteFile(strFile) -- Delete only export Media and Gedcom files end elseif tblAttr.mode == "directory" then doEmptyFolder(strFile) -- Empty subfolder before it can removed fhSleep(100) local isOK, strError = lfs.rmdir(strFile) -- Remove all export Media subfolders if not isOK then iup_gui.MemoDialogue("Cannot Remove Folder: "..tostring(strError)..".\n"..strFile.."\n") end fhSleep(100) end end end end -- local function doEmptyFolder function btnDelete:action() -- Action for Empty Export Folder button setControlsActive("NO") doEmptyFolder(TblOption.Path) -- V2.9 setControlsActive("YES") end -- function btnDelete:action function tglFolder:action(intState) -- Action for keep Media folder toggle -- V2.9 setControlsActive("NO") TblOption.Keep = tglFolder.Value doSaveSettings() setToolTip(tglFolder) setControlsActive("YES") end -- function tglFolder:action local function doMediaFolder() -- Create the Media sub-folder structure -- V2.9 if TblOption.Keep == "ON" then local getPath = StrPath.."Media" local putPath = TblOption.Path.."\\Media" if not general.MakeFolder(putPath,iup_gui.MemoDialogue) then return false end for strPath, tblAttr in general.DirTree(getPath) do if tblAttr.mode == "directory" then -- Must create any missing folders in Export folder strPath = strPath:replace(getPath,putPath) if not general.MakeFolder(strPath,iup_gui.MemoDialogue) then return false end end end end return true end -- local function doMediaFolder function btnExport:action() -- Action for Export GEDCOM & Media button setControlsActive("NO") if not doMediaFolder() then return iup.CLOSE end -- Create the Media sub-folder structure -- V2.9 if TblMode[TblOption.Mode].Abbr == "FMP" and TblOption.FMP.Zip ~= TblOption.Obje then doEmptyFolder(StrZipFMP) -- Empty FMP Zip Media folder if Media mode changes -- V3.3 TblOption.FMP.Zip = TblOption.Obje end getSpinValues() setRules(tblMode) -- Reset the Rules according to existing tblMode local isOK = ExportGedcom(doAnalyse,useRules) -- Export GEDCOM file using the Analyse & Rules functions setControlsActive("YES") if isOK then return iup.CLOSE end end -- function btnExport:action function tglTidying:action(intState) -- Action for tidying Place and Address toggle -- V3.3 setControlsActive("NO") TblOption[strMode].Tidy = tglTidying.Value doSaveSettings() setToolTip(tglTidying) setControlsActive("YES") end -- function tglTidying:action function tglFactDef:action(intState) -- Action for export Fact Set Definitions toggle -- V3.3 setControlsActive("NO") TblOption[strMode].Fact = tglFactDef.Value doSaveSettings() setToolTip(tglFactDef) setControlsActive("YES") end -- function tglFactDef:action function tglPrefix:action(intState) -- Action for include Synthetic Prefix toggle -- V3.3 setControlsActive("NO") TblOption[strMode].Pref = tglPrefix.Value doSaveSettings() setToolTip(tglPrefix) setControlsActive("YES") end -- function tglPrefix:action function tglCaption:action(intState) -- Action for include Avoid Repeat Captions toggle -- V3.3 setControlsActive("NO") TblOption[strMode].Capt = tglCaption.Value doSaveSettings() setToolTip(tglCaption) setControlsActive("YES") end -- function tglCaption:action local function doResetTab(tblTab) -- Reset the Extra/Other Options tab Rules for intTag, strTag in ipairs ( tblTab ) do TblOption[strMode][strTag] = nil end tblMode = setRules() -- Set default dictionary of Rules for current GEDCOM mode setControls() -- Reset controls & redisplay Main dialogue iup_gui.ShowDialogue("Main") end -- local function doResetTab function btnBasics:action() -- Action for Reset Options button on Basic Options tab -- V4.0 setControlsActive("NO") ResetGedcomOptions(TblMode[TblOption.Mode],TblOption[strAbbr].Full,"NotToggles") setControls() -- Reset controls & redisplay Main dialogue setControlsActive("YES") end -- function btnBasics:action function btnExtras:action() -- Action for Reset Options button on Extra Options tab setControlsActive("NO") for intItem, strItem in ipairs ( ArrOptionToggles ) do -- V3.3 TblOption[strMode][strItem] = TblMode[TblOption.Mode][strItem] end doResetTab(tblMode.Drop) setControlsActive("YES") end -- function btnExtras:action function btnOthers:action() -- Action for Reset Options button on Other Options tab setControlsActive("NO") doResetTab(tblMode.Togg) setControlsActive("YES") end -- function btnOthers:action local function doResetLabels() -- Reset all synthetic Labels options -- V4.0 setControlsActive("NO") ResetSyntheticLabels() tblMode = setRules() -- Set default dictionary of Rules for default GEDCOM mode setControls() -- Reset controls & redisplay Main dialogue setControlsActive("YES") iup_gui.ShowDialogue("Main") end -- local function doResetLabels function btnLabelA:action() -- Action for Reset Options button on Labels Set A tab -- V3.3 -- V4.0 doResetLabels() end -- function btnLabelA:action function btnLabelB:action() -- Action for Reset Options button on Labels Set B tab -- V3.3 -- V4.0 doResetLabels() end -- function btnLabelB:action function btnDefault:action() -- Action for Restore Defaults button local intKey = iup_gui.WarnDialogue(" Note All Settings ","\nHave you noted all non-default settings\nas they are all about to be reset to defaults?\n\n","No, Cancel the Reset","Yes, Continue with Reset") if intKey == 2 then setControlsActive("NO") general.SaveStringToFile("",iup_gui.ProjectFile) -- Erase the Plugin sticky data .dat file (general.DeleteFile(iup_gui.ProjectFile) fails) -- V3.7 ResetDefaultSettings() iup_gui.ShowDialogue("Help") tabControl.ValuePos = math.max(0,IntTabPosn - 1) -- Adjust tab selection tblMode = setRules() -- Set default dictionary of Rules for default GEDCOM mode setControls() -- Reset controls & redisplay Main dialogue setControlsActive("YES") iup_gui.ShowDialogue("Main") end end -- function btnDefault:action function btnSetFont:action() -- Action for Set Window Font button setControlsActive("NO") btnSetFont.Active = "NO" iup_gui.FontDialogue(tblControls) doSaveSettings() -- Save sticky data settings btnSetFont.Active = "YES" setControlsActive("YES") end -- function btnSetFont:action function btnSetFont:button_cb(intButton,intPress) -- Action for mouse right-click on Set Window Fonts button if intButton == iup.BUTTON3 and intPress == 0 then iup_gui.BalloonToggle() -- Toggle tooltips Balloon mode end end -- function btnSetFont:button_cb local function doExecute(strExecutable, strParameter) -- Invoke FH Shell Execute API -- V3.1 local function ReportError(strMessage) iup_gui.WarnDialogue( "Shell Execute Error", "ERROR: "..strMessage.." :\n"..strExecutable.."\n"..strParameter.."\n\n", "OK" ) end -- local function ReportError return general.DoExecute(strExecutable, strParameter, ReportError) end -- local function doExecute local strHelp = "https://pluginstore.family-historian.co.uk/page/help/export-gedcom-file" local arrHelp = { "-basic-options-tab"; "-extra-options-tab"; "-other-options-tab"; "-labels-set-tab"; "-labels-set-tab"; } function btnGetHelp:action() -- Action for Help & Advice button according to current tab local strPage = arrHelp[IntTabPosn] or "" doExecute( strHelp..strPage ) fhSleep(3000,500) dialogMain.BringFront="YES" end -- function btnGetHelp:action function btnDestroy:action() -- Action for Close button getSpinValues() doSaveSettings() -- V3.3 return iup.CLOSE end -- function btnDestroy:action function tabControl:tabchangepos_cb(intNew,intOld) -- Call back when Main tab position is changed IntTabPosn = intNew + 1 if intNew == 0 then lstEncode.Value = TblOption.Char -- Temp aid for "TNG" & "ANSI" problem end if intNew == 1 then btnExtras.Title = "Reset these "..strMode.." Options" -- Update reset button Title for current GEDCOM export mode end if intNew == 2 then btnOthers.Title = "Reset these "..strMode.." Options" -- Update reset button Title for current GEDCOM export mode end end -- function tabControl:tabchangepos_cb progbar.Step(1) doPopulateDropLists() -- Populate the GUI droplists progbar.Step(1) doPopulateToggles() -- Populate the GUI toggles progbar.Step(1) doPopulateLabels() -- Populate the GUI labels -- V3.3 progbar.Step(1) fhSleep(100,60) if progbar.Stop() then return end iup_gui.ShowDialogue("Main",dialogMain,btnDestroy,"map") -- Map the Main GUI Dialogue so window size of earlier version is corrected progbar.Step(4) fhSleep(100,60) if progbar.Stop() then return end iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes progbar.Step(4) fhSleep(100,60) if progbar.Stop() then return end progbar.Close() iup_gui.ShowDialogue("Main",dialogMain,btnDestroy,"show") -- Display Main GUI Dialogue and optionally Version History Help end -- function GUI_MainDialogue --[[ Export Gedcom File Strategy: After setting up local variables, each line is read from the GEDCOM import file in two passes. In both passes the lines are inserted into a Record array, and processed Record by Record. This allows all lines in the Record to be processed as a single entity. Fast Analysing 1st Pass with doAnalyse(): Optionally for large GEDCOM the Progress Bar is started showing each Record Type and Id. Collects a few statitics for Pass 2: Total number of Records count is used by the Pass 2 Progress Bar. Largest Id for Note & Source Records are needed to synthesise new Note & Source Records. Creates a dictionary of every Record Name/Title, where lookup key is Record Id. This allows a Name/Title to replace a Record Id in various Pass 2 rules. Creates a dictionary of every Media ASID Object, where lookup key is ASID + Multimedia Record Id. This holds all relevant tag lines for Pass 2 and allows all Multimedia Records to be deleted. Creates a dictionary of Place Names & Record Id to Source Record Id mapping for Pass 2. This allows Place fields to cite Source Records derived from Place Records. Creates a table of Witnessed Facts to copy to each Witness in Pass 2. Rule is created with Fact index for each Witness Individual record. Creates a table of Source record Media to copy to Citations for FTL/FTM. Slow Exporting 2nd Pass with useRules(): Optionally for many Records the Progress Bar is started showing each Record Type and Id. Rule processing is invoked per Record, which includes converting Media files. See the useRules() function for further details. Character encoding is applied and the Record written to the GEDCOM export file. Finally the Result Set is output, unless the export was cancelled. --]] -- Progress bar Message per Record with its Tag and Id -- function ProgressMessage(strMessage,strRecord) strRecord = strRecord:match("( _?%u+)").." "..(strRecord:match("@%u(%d+)@") or "") progbar.Message(strMessage..strRecord) end -- function ProgressMessage -- Export Gedcom File -- function ExportGedcom(doAnalyse,useRules) -- doAnalyse = Analyse function to save record names, _ROOT, Media _ASID/_SEQ, Witnessed Facts, etc -- useRules = Rules function to convert records SetSharedData() -- V4.0 local tblMain = iup_gui.DialogueAttributes("Main") -- Obtain position of Main dialogue local intMainX = tblMain.CoordX local intMainY = tblMain.CoordY local strFull = " "..TblMode[TblOption.Mode].Full -- Full name of chosen Mode of GEDCOM Export local strMode = " "..TblMode[TblOption.Mode].Abbr -- Abbreviated export mode "Std", "Min", "FTA", "TNG", etc local strCode = " "..strChar -- Character encoding code "ANSI", "ISO", "UTF8", etc -- V4.0 strChar local strBOM = TblChar[TblOption.Char].BOM -- Character encoding Byte Order Mark file prefix for 1st record local doEncode = TblChar[TblOption.Char].Encode -- Function to translate CP1252 to ANSI or ISO or UTF-8 local strExport = StrFile..strMode..strCode..".ged" -- GEDCOM export file name & type local putExport = general.OpenFile(TblOption.Path.."\\"..strExport,"wb") local strOption = strFull..strCode -- Chosen export mode and encoding local intRecord = 0 -- Record count for Progress Bar local arrRecord = { } -- Array of text lines in a record local arrLineNo = { } -- Array of their import line numbers local intLineNo = 0 -- Line number of each line in import file local intBytes = lfs.attributes(StrImport,"size") if intBytes > 5000000 then progbar.Setup( { X = intMainX+50; Y = intMainY+50; } ) -- Popup in same place as Main dialogue progbar.Start("Analysing GEDCOM",intBytes) -- Start Progress Bar if significant file size end local isConflicts, arrConflict -- Conflicting Place Record Names signal, array, and Result Set local arrPlace1 = {} local arrRecId1 = {} local arrPlace2 = {} local arrRecId2 = {} for strText,strTail,intLump in encoder.FileLines(StrImport,"UTF-8") do if intLump > 0 then -- Fast 1st Pass ~ Analyse each text line from FH GEDCOM import file progbar.Step(intLump) if progbar.Stop() then break end -- Cancel Analysing? end if strText:match("^0 [@T]") then -- Next record text line head detected ProgressMessage("Analysing input GEDCOM for",arrRecord[1]) intRecord = intRecord+1 -- Count all records for 2nd Pass Progress Bar isConflicts, arrConflict = doAnalyse(arrRecord,arrLineNo) -- Create record Name/Title dictionary & Media ASID Object dictionary & Witness Facts array, get record largest Ids, etc -- V1.8 arrRecord = {} -- Clear array for next record arrLineNo = {} collectgarbage("step",0) -- Improves run time! -- V4.2 end intLineNo = intLineNo + 1 table.insert(arrRecord,strText) -- Add text line to record array table.insert(arrLineNo,intLineNo) -- Add its import file line number end if isConflicts then progbar.Close() for intItem, arrItem in ipairs (arrConflict) do -- Compose Conflicting Place Record Names Result Set local ptrPlace1 = fhNewItemPtr() local ptrPlace2 = fhNewItemPtr() ptrPlace1:MoveToRecordById("_PLAC",arrItem[1]) ptrPlace2:MoveToRecordById("_PLAC",arrItem[2]) table.insert(arrPlace1,ptrPlace1:Clone()) table.insert(arrRecId1,arrItem[1]) table.insert(arrPlace2,ptrPlace2:Clone()) table.insert(arrRecId2,arrItem[2]) end fhOutputResultSetTitles(iup_gui.Plugin..iup_gui.Version.."~ Conflicting Place Record Names") fhOutputResultSetColumn("Place Name" ,"item",arrPlace1,#arrPlace1,140,"align_left",0) fhOutputResultSetColumn("Rec Id" ,"integer",arrRecId1,#arrPlace1, 40,"align_mid" ,1,true,"integer") fhOutputResultSetColumn("Place Name" ,"item",arrPlace2,#arrPlace1,140,"align_left",0) fhOutputResultSetColumn("Rec Id" ,"integer",arrRecId2,#arrPlace1, 40,"align_mid" ,0) return true -- Signal export terminated (true) end if progbar.Stop() then progbar.Close() -- Analysis cancelled - V1.4 ResetResultSet() -- Clear the Result Set arrays iup_gui.MemoDialogue("\nExport GEDCOM File has been Cancelled\n") return false -- Signal export cancelled (false) end progbar.Close() if intRecord > 200 then progbar.Setup( { X = intMainX+50; Y = intMainY+50; } ) -- Popup in same place as Main dialogue progbar.Start("Exporting GEDCOM",intRecord) -- Start Progress Bar if significant records end arrRecord = { } -- Clear arrays for first record arrLineNo = { } intLineNo = 0 for strText in encoder.FileLines(StrImport,"UTF-8") do -- Slow 2nd Pass ~ Read each text line from FH GEDCOM import file -- V1.5 Unicode if strText:match("^0 [@T]") then -- Next record text line head detected ProgressMessage("Exporting"..strOption,arrRecord[1]) if useRules(arrRecord,arrLineNo) then -- Use rules to convert a record, returns false to remove record entirely -- V1.3 local strRecord = doEncode(table.concat(arrRecord,"\r\n").."\r\n") putExport:write(strBOM..strRecord) -- Write encoded record to export GEDCOM file (with optional BOM on 1st record) strBOM = "" -- Clear Byte Order Mark (BOM) file prefix -- V1.8 end arrRecord = {} -- Clear arrays for next record arrLineNo = {} progbar.Step(1) if progbar.Stop() then break end -- Cancel Exporting? collectgarbage("step",0) -- Improves run time! -- V4.2 end intLineNo = intLineNo + 1 table.insert(arrRecord,strText) -- Add text line to record array table.insert(arrLineNo,intLineNo) -- Add its import file line number end --? useRules(arrRecord,arrLineNo) -- V1.8 in case need TRLR rule local strRecord = doEncode(table.concat(arrRecord,"\r\n").."\r\n") -- Write last record (usually trailer "0 TRLR") putExport:write(strRecord) -- V2.8 needed for correct line terminator putExport:close() -- Close GEDCOM export file iup_gui.CustomDialogue("Memo","x",intMainX+50,intMainY+50) -- Dialogue window size and position if progbar.Stop() then progbar.Close() -- Export cancelled - V1.4 ResetResultSet() -- Clear the Result Set arrays iup_gui.MemoDialogue("\nExport GEDCOM File has been Cancelled\n") return false -- Signal export cancelled (false) end progbar.Close() local intKey = iup_gui.MemoDialogue(strOption.." file has been written to export folder in: \n\n "..strExport.." \n\n View this file in Family Historian, Notepad, or Windows/File Explorer ? \n\n BEWARE: Using Family Historian may disrupt Gedcom file format ! ! ! \n"," Family\nHistorian","Windows\nNotepad","Win/File\nExplorer"," Close\nMemo") if intKey == 1 then general.DoExecute(TblOption.Path.."\\"..strExport) -- V1.4 elseif intKey == 2 then general.DoExecute("notepad.exe",TblOption.Path.."\\"..strExport) elseif intKey == 3 then general.DoExecute(TblOption.Path) -- V1.4 end if #ArrRule > 0 then -- Output Result Set arrays fhSetStringEncoding("UTF-8") -- V4.2 fhOutputResultSetTitles(iup_gui.Plugin..iup_gui.Version.."~"..strOption) fhOutputResultSetColumn("No." ,"integer",ArrSort,#ArrRule, 24,"align_mid" ,1,true,"integer") fhOutputResultSetColumn("Rule" ,"integer",ArrRule,#ArrRule, 24,"align_mid" ,2,true,"integer") -- V4.5 fhOutputResultSetColumn("Title" ,"text" ,ArrTitl,#ArrRule, 70,"align_left",0) fhOutputResultSetColumn("Action" ,"text" ,ArrItem,#ArrRule,110,"align_left",0) fhOutputResultSetColumn("Record" ,"text" ,ArrRecd,#ArrRule, 70,"align_left",0) fhOutputResultSetColumn("Buddy" ,"item" ,ArrPntr,#ArrRule, 70,"align_left",0,true,"default","buddy") fhOutputResultSetColumn("Line" ,"integer",ArrLine,#ArrRule, 36,"align_mid" ,3,true,"integer") -- V4.5) fhOutputResultSetColumn("Project","text" ,ArrOrig,#ArrRule,230,"align_left",0) -- V4.0 230 fhOutputResultSetColumn("Export" ,"text" ,ArrText,#ArrRule,999,"align_left",0) -- V4.0 300 end return true -- Signal export completed (true) end -- function ExportGedcom -- Main code starts here -- local intPause = collectgarbage("setpause",50) -- Default = 200 Aggressive = 50 -- Sets pause of collector and returns prev value of pause -- V4.5 local intStep = collectgarbage("setstepmul",300) -- Default = 200 Aggressive = 300 -- Sets step mult of collector & returns prev value of step -- V4.5 fhInitialise(5,0,0,"save_recommended") -- Recommend current GEDCOM is saved if outstanding changes PresetGlobalData() -- Preset global data definitions ResetDefaultSettings() -- Preset default sticky settings LoadSettings() -- Load sticky data settings iup_gui.CheckVersionInStore() -- Notify if later Version available GUI_MainDialogue() -- Invoke main dialogue SaveSettings() -- Save sticky data settings if IntFhVersion > 5 then fhSetConversionLossFlag(false) end -- Inhibit loss of accents message

Source:Export-Gedcom-File-8.fh_lua

'..(strRow:gsub('|','

'):gsub('\r','

'))..'

%s-([^`]-)

%1

%s-([^`]-)

%1

%s-([^`]-)

%1