Export Gedcom File.fh_lua--[[
@Title: Export Gedcom File
@Type: Standard
@Author: Mike Tate
@Contributors:
@Version: 6.4
@Keywords:
@LastUpdated: 28 Jul 2025
@Licence: This plugin is copyright (c) 2025 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.
]]
--[=[
--? Special cases, or diagnostics, etc.
TBD: Review GFT for Media Picture Note settings in several rules; Use fhCallBuiltInFunction("TextPart",strText,1,0,"TIDY"):gsub(" +"," "):gsub(" +,",",") in strTidyText();
TBD: Update (BHG) Behold Genealogy https://www.beholdgenealogy.com/blog/?p=3232; Translate labels such as Principal as per Language; doAnalyse() use FH API rather than GEDCOM file; doPrune() < > to < > substitution;
@V6.4: Fix doPrune() strAny bug when level & tag missing; 1 FACT/_ATTR space bug in doSaveFacts();
@V6.3: Fix doWitness(...) _FMT bug; Cater for media\ Media/ media/;
@V6.2: TNG image CROP (FULL~...) feature; Ensure annotation notes get removed;
@V6.1: Library V3.7; Extremely long text limit according to destination and improved error report;
@V6.0: Library V3.6; Improve the Tidy Place Record Name Conflicts message; doEmptyFolder(...) uses general.GetFolderContents(...); Make doCopyFile(...) global; Support (GKP) GEDKeeper Program; Set excessive text to 10,000 chars for GKP; (MYH) MyHeritage to GEDCOM 5.5.1 except FACT tag; Option for / path separators for MacOS, etc;
@V5.9: Fix problems loading Fact Set files with accents/symbols in file path &/or are empty files; Update ANC for Note Citations and Facts with no Date, Place & Note fields; doMediaError(...) function uses first 15 chars to identify similar mesages;
@V5.8: Fix problem in general.MakeFolder(...) when folder path is invalid;
@V5.7: Fix for FH V5 Registry PDX Font and fhSetStringEncoding(...) missing; Omit Source Template Note record if Source Template label is blank; In doReform(..) omit Footnote/Bibliography if blank Label; Fix doSrcTempCit(..) & doSrcTempRec(...) conversion to Notes; Add GST to allow formatted Source record Title;
@V5.6: Add _QUAL Other Option for doAssess(...); New _SHAR Move to Fact Note Min option for TNG with hyperlinks for Tree ID in HEAD.DEST; Improved Fact Witness Facts; Improved closing Memo; GedSite same-sex Asymmetric Format;
@V5.5: In doSrcTempLnk(): Source Title style codes for TNG as well as RMT, add Footnote, Short Footnote & Bibliography labelled Source Note using dummy records;
@V5.4: Exclude 'Book' & 'Title' style code setup in PresetGlobalData() for FH V5/6; Use HKCU Output Language Id to include lang-facts.fhf in exported Sentence Templates;
@V5.3: Fix doInsert() line bug; doConJoin() with table copes with long text; doDisJoin() avoids break on UTF8 and traps excessively long text; Fix FORMat when TblOption.Jpeg == "ON"; WebTag option for _WEBTAG for Source Citation URL; Style codes in Title in doSrcTempLnk(); Add Weblink & Record label options; New rule Text from Source to Census Note doTfS2Note();
@V5.2: Trap errors in doCopyFile(); Remove redundant commas in doKeyword(); Remove _FMT tag from Fact Witness Notes in doWitness(); Omit synthetic Place citation Note in doCite2Note(); Convert REPO even for Remove entirely in doSrcTempLnk(); Fix citation _FIELD bug in doSrcTempCit(); Fix missing Note bug in doSrcTempRec();
@V5.1: BHG placeholder; _ATTR/FACT: Custom Fact/Event & Value for ANC; BET/AND to Qn date option for TNG; BAPM to CHR option for TNG;
@V5.1: Library V3.3; Fix Help for Manage Settings tab; Added DetectOldModules(); Fix Export folder issue found by BillH Mar'22; Fix empty FMP ZIP folder found by jelv Jul'22; Fix doPrune() for rich text;
@V5.0: Library V3.2 FSO Unicode; Synthetic Birth/Death from Bap/Chr/Bur/Crem, etc; _UID formatting; Fix rich text in synthetic Notes; New GUI option names; Manage Settings tab for Global v Project settings & Copy product settings to/from CEA/B;
@V4.6: CheckVersionInStore() at monthly intervals; Citation NOTE Weblink adds _LINK URL & fix copy Media to Citations for FTM;
@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.
]=]
if fhGetAppVersion() > 5 then
fhSetStringEncoding("UTF-8")
end
--[[
@Title: aa Library Functions Preamble
@Author: Mike Tate
@Version: 3.7
@LastUpdated: 12 Dec 2024
@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+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.5
@LastUpdated: 12 Dec 2024
@Description: A general functions module to supplement LUA functions, where filenames use UTF-8 but for a few exceptions.
@V3.5: Further fix for Crossover/WINE file attributes;
@V3.4: Further fix for Unix/WINE file attributes;
@V3.3: Fix problem in fh.MakeFolder(...) when folder path is invalid; Remove Type, ShortName & ShortPath from attributes(...) as unsupported in WINE;
@V3.2: Added function DetectOldModules(); Updated functions RenameFile(), RenameFolder() & GetFolderContents();
@V3.1: Functions derived from FH V7 fhFileUtils library using File System Objects, plus additional features;
@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("luacom") -- To create File System Object
fh.FSO = luacom.CreateObject("Scripting.FileSystemObject")
-- Report error message --
local function doError(strMessage,errFunction)
-- strMessage ~ error message text
-- errFunction ~ optional error reporting function
if type(errFunction) == "function" then
errFunction(strMessage)
else
error(strMessage)
end
end -- local function doError
-- Convert filename to ANSI alternative and indicate success --
function fh.FileNameToANSI(strFileName,strAnsiName)
-- strFileName ~ full file path
-- strAnsiFile ~ ANSI file name & type
-- return values ~ ANSI file path, true if original path was ANSI compatible
if stringx.encoding() == "ANSI" then return strFileName, true end
local isFlag = fhIsConversionLossFlagSet()
fhSetConversionLossFlag(false)
local strAnsi = fhConvertUTF8toANSI(strFileName)
local wasAnsi = true
if fhIsConversionLossFlagSet() then
strAnsiName = strAnsiName or "ANSI.ANSI"
strAnsi = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugin Data\\"..strAnsiName
wasAnsi = false
end
fhSetConversionLossFlag(isFlag)
return strAnsi, wasAnsi
end -- local function FileNameToANSI
-- Get parent folder --
function fh.GetParentFolder(strFileName)
-- strFileName ~ full file path
-- return value ~ parent folder path
local strParent = fh.FSO:GetParentFolderName(strFileName) --! Faulty in FH v6 with Unicode chars in path
if fhGetAppVersion() == 6 then
local _, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
strParent = strFileName:match("^(.+)[\\/][^\\/]+[\\/]?$")
end
end
return strParent
end -- function GetParentFolder
-- Check if file exists --
function fh.FlgFileExists(strFileName)
-- strFileName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FileExists(strFileName)
end -- function FlgFileExists
-- Check if folder exists --
function fh.FlgFolderExists(strFolderName)
-- strFolderName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FolderExists(strFolderName)
end -- function FlgFolderExists
-- Delete a file if it exists --
function fh.DeleteFile(strFileName,errFunction)
-- strFileName ~ full file path
-- errFunction ~ optional error reporting function
-- return value ~ true if file does not exist or is deleted else false
if fh.FSO:FileExists(strFileName) then
fh.FSO:DeleteFile(strFileName,true)
if fh.FSO:FileExists(strFileName) then
doError("File Not Deleted:\n"..strFileName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFile
-- Delete a folder if it exists including contents --
function fh.DeleteFolder(strFolderName,errFunction)
-- strFolderName ~ full folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder does not exist or is deleted else false
if fh.FSO:FolderExists(strFolderName) then
fh.FSO:DeleteFolder(strFolderName,true)
if fh.FSO:FolderExists(strFolderName) then
doError("Folder Not Deleted:\n"..strFolderName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFolder
-- Rename a file if it exists --
function fh.RenameFile(strFileName,strNewName)
-- strFileName ~ full file path
-- strNewName ~ new file name & type
-- return value ~ true if file exists but new name does not and rename is OK else false
local strNewFile = fh.GetParentFolder(strFileName).."\\"..strNewName
if fh.FSO:FileExists(strFileName) and not fh.FSO:FileExists(strNewFile) then
local fileObject = fh.FSO:GetFile(strFileName)
fileObject.Name = strNewName
if fh.FSO:FileExists(strNewFile) then
return true
end
end
return false
end -- function RenameFile
-- Rename a folder if it exists --
function fh.RenameFolder(strFolderName,strNewName)
-- strFolderName ~ full folder path
-- strNewName ~ new folder name
-- return value ~ true if folder exists but new name does not and rename is OK else false
local strNewFolder = fh.GetParentFolder(strFolderName).."\\"..strNewName
if fh.FSO:FolderExists(strFolderName) and not fh.FSO:FolderExists(strNewFolder) then
local folderObject = fh.FSO:GetFolder(strFolderName)
folderObject.Name = strNewName
if fh.FSO:FolderExists(strNewFolder) then
return true
end
end
return false
end -- function RenameFolder
-- Copy a file if it exists and destination is not a folder --
function fh.CopyFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
fh.FSO:CopyFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
return false
end -- function CopyFile
-- Copy a folder if it exists and destination is not a file --
function fh.CopyFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
fh.FSO:CopyFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
return false
end -- function CopyFolder
-- Move a file if it exists and destination is not a folder --
function fh.MoveFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
if fh.DeleteFile(strDestination) then
fh.FSO:MoveFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFile
-- Move a folder if it exists and destination is not a file --
function fh.MoveFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
if fh.DeleteFolder(strDestination) then
fh.FSO:MoveFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFolder
local function CreateFolder(strFolderName) -- V3.3
fh.FSO:CreateFolder(strFolderName)
end -- local function CreateFolder(strFolderName)
-- Make subfolder recursively if does not exist --
function fh.MakeFolder(strFolderName,errFunction)
-- strFolderName ~ full source folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder exists or created else false
if not fh.FSO:FolderExists(strFolderName) then
if #strFolderName > 4 -- V3.3
and not fh.MakeFolder(fh.GetParentFolder(strFolderName),errFunction) then
return false
end
if not pcall(CreateFolder,strFolderName) -- V3.3
or not fh.FSO:FolderExists(strFolderName) then
doError("Cannot Make Folder Path: \n"..strFolderName.." \n",errFunction)
return false
end
end
return true
end -- function MakeFolder
-- Check if folder writable --
function fh.FlgFolderWrite(strFolderName)
-- strFolderName ~ full source folder path
-- return value ~ true if folder writable else false
if fh.FlgFolderExists(strFolderName) then
if fh.MakeFolder(strFolderName.."\\vwxyz") then
fh.FSO:DeleteFolder(strFolderName.."\\vwxyz",true)
return true
end
end
return false
end -- function FlgFolderWrite
-- Open File with ANSI path and return Handle --
function fh.OpenFile(strFileName,strMode)
-- strFileName ~ full file path
-- strMode ~ "r", "w", "a" optionally suffixed with "+" &/or "b"
-- return value ~ file handle
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(strContents,strFileName,strFormat)
-- strContents ~ text string
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ true if successful else false
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhSaveTextFile(strFileName,strContents,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
local fileHandle = fh.OpenFile(strAnsi,"w")
fileHandle:write(strContents)
assert(fileHandle:close())
if not wasAnsi then
fh.MoveFile(strAnsi,strFileName)
end
return true
end -- function SaveStringToFile
-- Load string from file --
function fh.StrLoadFromFile(strFileName,strFormat)
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ file contents
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhLoadTextFile(strFileName,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
fh.CopyFile(strFileName,strAnsi)
end
local fileHandle = fh.OpenFile(strAnsi,"r")
local strContents = fileHandle:read("*all")
assert(fileHandle:close())
return strContents
end -- function StrLoadFromFile
-- Returns the Path, Filename, and Extension as 3 values --
function fh.SplitFilename(strFileName)
-- strFileName ~ full file path
-- return values ~ path, name.type, type
if fh.FSO:FolderExists(strFileName) then
local strPath = strFileName:gsub("[\\/]$","")
return strPath.."\\","",""
end
strFileName = strFileName.."."
return strFileName:match("^(.-)([^\\/]-%.([^\\/%.]-))%.?$")
end -- function SplitFilename
-- Convert dd/mm/yyyy hh:mm:ss format to integer seconds -- (DateTime format is used in attributes returned by GetFolderContents and DirTree below)
function fh.IntTime(strDateTime)
-- strDateTime ~ date time string
-- return value ~ integer seconds since 01/01/1970 00:00:00
local strDay,strMonth,strYear,strHour,strMin,strSec = strDateTime:match("^(%d%d)/(%d%d)/(%d+) (%d%d):(%d%d):(%d%d)")
if tonumber(strYear) < 1970 then return 0 end
local isDST = false
if tonumber(strMonth) > 4 and tonumber(strMonth) < 11 then isDST = true end -- Approximation is sometimes wrong
local intTime = os.time( { year=strYear; month=strMonth; day=strDay; hour=strHour; min=strMin; sec=strSec; isdst=isDST; } )
local tblDat = os.date("*t",intTime)
if tblDat.isdst then
intTime = intTime + 3600
isDST = true
end
return intTime
end -- function IntTime
-- Return table of attributes for folder --
local function attribFolder(tblAttr)
-- tblAttr ~ folder attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE only supports the tblAttr.name & tblAttr.path
return { mode="directory"; name=tblAttr.name; path=tblAttr.path; }
end -- local function attribFolder
-- Return table of attributes for file --
local function attribFile(tblAttr)
-- tblAttr ~ file attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE does not support the tblAttr.type, tblAttr.shortname, tblAttr.shortpath & sometimes tblAttr.datecreated
return { mode="file"; name=tblAttr.name; path=tblAttr.path; size=tblAttr.size; modified=tblAttr.datelastmodified; attributes=tblAttr.attributes; }
end -- local function attribFile
-- Return attributes table of all files and folders in a specified folder --
function fh.GetFolderContents(strFolder,doRecurse)
-- strFolder ~ full folder path
-- doRecurse ~ true for recursion
-- return value ~ attributes table
local arrList = {}
if fh.FSO:FolderExists(strFolder) then
local function getFileList(strFolder)
local tblList = fh.FSO:GetFolder(strFolder)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFolder(tblAttr))
if doRecurse then getFileList(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFile(tblAttr))
tblAttr = tblEnum:Next()
end
end
getFileList(strFolder)
end
return arrList
end -- function GetFolderContents
-- Return a Directory Tree entry & attributes on each iteration --
function fh.DirTree(strDir,...)
-- strDir ~ full folder path
-- ... ~ list of folders to omit
-- return value ~ full path, attributes table
local arg = {...}
assert( fh.FSO:FolderExists(strDir), "directory parameter is missing or empty" )
local function yieldtree(strDir)
local tblList = fh.FSO:GetFolder(strDir)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.SubFolders) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"directory"))
local isOK = true
for _,strOmit in ipairs (arg) do
if tblAttr.path:match(strOmit) then -- Omit tree branch
isOK = false
break
end
end
if isOK then yieldtree(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.Files) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"file"))
tblAttr = tblEnum:Next()
end
end
return coroutine.wrap(function() yieldtree(strDir) end)
end -- function DirTree
-- Detect FH V5/6 old library modules and advise removal --
function fh.DetectOldModules()
if fhGetAppVersion() > 6 then
local strPath = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugins\\"
local arrFile = { "compat53.lua"; "ltn12.lua"; "luasql\\sqlite3.dll"; "md5.lua"; "pl\\init.lua"; "socket.lua"; "utf8.lua"; "zip.dll"; }
for _, strFile in ipairs (arrFile) do
if fh.FSO:FileExists(strPath..strFile) then
fhMessageBox("\n Detected some old FH V6 library modules. \n\nPlease remove them by running the plugin: \n\n 'Delete old FH6 Plugin Module Files' \n","MB_OK","MB_ICONEXCLAMATION")
break
end
end
end
end -- function DetectOldModules
if fhGetAppVersion() > 6 then unpack = table.unpack end
-- Invoke FH Shell Execute API --
function fh.DoExecute(strExecutable,...)
-- strExecutable ~ full path of executable
-- ... ~ parameter list and optional error reporting function
-- return value ~ true if successful else false
local arg = {...}
local errFunction = fhMessageBox
if type(arg[#arg]) == 'function' then
errFunction = arg[#arg]
table.remove(arg)
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)
-- datDate ~ date point
-- return value ~ day number
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,"MB_OK","MB_ICONEXCLAMATION")
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
-- return value ~ integer year 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
-- ptrIndi ~ pointer to individual
-- return value ~ pointer to fact
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
-- return values ~ EARLIEST, MID, LATEST 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)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
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)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
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+tablex_v3
@Author: Mike Tate
@Version: 3.1
@LastUpdated: 08 Jan 2022
@Description: A Table Load Save Module.
@V3.1: Cater for full UTF-8 filenames.
@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,_,stransi,wasansi -- V1.2 -- V3.1 -- Added _,stransi,wasansi --!
-- 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
stransi,wasansi = general.FileNameToANSI(filename) -- V3.1 -- Cater for non-ANSI filename --!
file,err = io.open( stransi, "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()
if not ( wasansi ) then -- V3.1 -- Cater for non-ANSI filename --!
general.MoveFile(stransi,filename)
end
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
local stransi,wasansi = general.FileNameToANSI(sfile) -- V3.1 -- Cater for non-ANSI filename --!
if not ( wasansi ) then
general.CopyFile(sfile,stransi)
end
tables,err = loadfile( stransi )
if not ( wasansi ) then
general.DeleteFile(stransi) -- V3.1 -- Cater for non-ANSI filename --!
end
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+encoder_v3
@Author: Mike Tate
@Version: 3.6
@LastUpdated: 27 Aug 2024
@Description: Text encoder module for HTML XHTML XML URI UTF8 UTF16 ISO CP1252/ANSI character codings.
@V3.6: In fh.FileLines(...) cater for empty file;
@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","MB_OK","MB_ICONEXCLAMATION")
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) or "" -- Read first lump from file and cater for empty 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" and #strText > 0 then -- Define ANSI conversion to current encoding and cater for empty file
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: 4.2
@LastUpdated: 07 Oct 2024
@Description: Graphical User Interface Library Module
@V4.2: Skip if standalone GEDCOM in fh.SaveSettings() and getDataFiles();
@V4.1: CheckVersionInStore() save & retrieve latest version in file; Remove old wiki Help features;
@V4.0: Cater for full UTF-8 filenames;
@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
--]]
-- 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")
iup.SetGlobal("UTF8MODE_FILE","NO") -- V4.0
function fh.SetUtf8Mode() -- Set IUP into UTF-8 mode
if iupVersion == "3.5" or stringx.encoding() == "ANSI" then return false end
iup.SetGlobal("UTF8MODE","YES")
iup.SetGlobal("UTF8MODE_FILE","YES") -- V4.0
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 = {}
local strClip = general.StrLoadFromFile(strFileName) --! -- V4.0
for strLine in strClip:gmatch("[^\r\n]+") do --! -- V4.0
if #tblField == 0
and strLine:match("^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
else
for i,j in pairs (tblClipData) do
tblClipData[i] = nil --! Restore defaults and clear any junk -- V4.0
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 #strFileName > 0 then -- Skip for standalone GEDCOM -- V4.2
if type(table.save) == "function" then -- Save entire Settings File table per Project/User/Machine
table.save(tblClipData,strFileName)
else
local tblClip = {}
for strKey,strVal in pairs(tblClipData) do -- Else save Settings File lines with key & val fields -- V4.0
table.insert(tblClip,strKey.."="..strVal.."\n") --! -- V4.0
end
local strClip = table.concat(tblClip,"\n") --! -- V4.0
if not general.SaveStringToFile(strClip,strFileName) then
error("\nSettings file not saved successfully.\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.")
end
end
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("%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 not strFrame:match("dialog") -- Inhibit MainLoop if "dialog" mode -- V4.1
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
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
local strErrFile = 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(strErrFile) -- 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. ","MB_OK","MB_ICONEXCLAMATION")
end
general.SaveStringToFile(strErrFile,strErrFile) -- Update file modified time
else
general.DeleteFile(strErrFile) -- Delete file if Internet is OK
if strReturn ~= nil then
strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits
general.SaveStringToFile(strVersion,strFile) -- Update file modified time and save version -- V4.1
end
end
else
strVersion = general.StrLoadFromFile(strFile) -- Retrieve saved latest version -- V4.1
if #strVersion > 9 then general.DeleteFile(strFile) end
end
end
return strVersion or "0"
end -- function VersionInStore
local function intVersion(strVersion) -- Convert version string to comparable integer
local intVersion = 0
local arrNumbers = {}
strVersion:gsub("(%d+)", function(strDigits) table.insert(arrNumbers,strDigits) end) -- V4.1
for i=1,5 do
intVersion = intVersion * 100 + tonumber(arrNumbers[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 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 strDataFile
end -- local function getPluginDataFileName
local function getDataFiles(strScope) -- Compose the Plugin Data file & path & root names
local strPluginName = fh.Plugin
local strPluginPlain = stringx.plain(strPluginName)
local strDataRoot = "" -- Plugin data file root name -- V4.2
local strDataPath = "" -- Plugin data folder path name
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 > 0 then -- Standalone GEDCOM path is ""
strDataPath = strDataFile:gsub("\\"..strPluginPlain.."%.[D,d][A,a][T,t]$","")
strDataRoot = strDataPath.."\\"..strPluginName
general.MakeFolder(strDataPath) -- V3.4
end
return strDataFile, strDataPath, strDataRoot
end -- local function getDataFiles
function fh.Initialise(strVersion,strPlugin) -- Initialise the GUI module with optional Version & Plugin name
local strAppData = 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 ""),"MB_OK","MB_ICONEXCLAMATION")
end
else
fhMessageBox("\nPlugin has not been saved!","MB_OK","MB_ICONEXCLAMATION")
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 = fhGetContextInfo("CI_PROJECT_DATA_FOLDER") -- Paths used by Load/SaveFolder for relative folders -- V4.0
fh.PublicPath = fhGetContextInfo("CI_PROJECT_PUBLIC_FOLDER") -- Public data folder path name -- V4.0
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"
local TblOption = {} -- Table of GUI sticky options
local TblMode = {} -- Table of Modes of GEDCOM Export
local TblChar = {} -- Table of Character Encodings
local TblObje = {} -- Table of Media Object options
local TblPriv = {} -- Table of [[private]] text options
local TblRich = {} -- Table of rich text format options
local TblName = {} -- Table of part/full frame naming options
local TblLabs = {} -- Table of default synthetic Note labels, etc
local ArrOptionToggles = { "Tidy"; "Fact"; "Pref"; "Capt"; "Make"; } -- Array of ON/OFF option toggle names -- V5.0
-- Preset Global Data Definitions --
function PresetGlobalData()
iup_gui.Gap = "2"
iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V4.0
IsIupUtf8 = iup_gui.SetUtf8Mode()
general.DetectOldModules() -- V5.1
IntFhVersion = fhGetAppVersion()
StrLessLess = "«" -- Legacy 9 rich text delimiters and to hide format style codes -- V5.3
StrMoreMore = "»"
StrBullet = "•" -- Bullet Blob for lists
StrSection = "§" -- Section Sign is @ substitute
StrPilcrow = " ¶ " -- Newline Pilcrow symbol
if IntFhVersion > 5 then
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
local arrStyle = { -- Define style codes for doSrcTempLnk() Reformat Metafields -- V5.3
{ Val=32; Pre='"' ; Suf='"' ; };
{ Val=16; Pre="'" ; Suf="'" ; };
{ Val= 8; Pre=""; Suf=" "; }; -- Each Windows Registry settings bit enables a style code
{ Val= 4; Pre=""; Suf=""; };
{ Val= 2; Pre=""; Suf=""; };
{ Val= 1; Pre=""; Suf=""; };
}
local strRegKey = iup_gui.GetRegKey("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\PROCESSOR_ARCHITECTURE")
local StrProcArch64 = not ( strRegKey == "x86" ) -- 64-bit or 32-bit PC for WOW6432Node support
local strKeyHKLM = "HKLM\\SOFTWARE\\" -- HKEY_LOCAL_MACHINE keys path
if StrProcArch64 then -- Insert WOW6432Node for 64-bit PC
strKeyHKLM = "HKLM\\SOFTWARE\\WOW6432Node\\"
end
strKeyHKLM = strKeyHKLM.."Calico Pie\\Family Historian\\2.0\\Preferences\\Report Footnote Style "
local intBookStyle = iup_gui.GetRegKey(strKeyHKLM.."Book") -- Tools > Preferences > Sources > 'Book' Style
local intTitlStyle = iup_gui.GetRegKey(strKeyHKLM.."Title") -- Tools > Preferences > Sources > 'Title' Style
local strBookStyle = "{%1}"
local strTitlStyle = "{%1}"
if intBookStyle and intTitlStyle then -- V5.4
for _, dicStyle in ipairs (arrStyle) do -- Compose style codes chosen by Windows Registry settings in FH V7
local intVal = dicStyle.Val
if intBookStyle - intVal >= 0 then
intBookStyle = intBookStyle - intVal
strBookStyle = dicStyle.Pre..strBookStyle..dicStyle.Suf
end
if intTitlStyle - intVal >= 0 then
intTitlStyle = intTitlStyle - intVal
strTitlStyle = dicStyle.Pre..strTitlStyle..dicStyle.Suf
end
end
end
StrBookStyle = "<"..strBookStyle..">" -- 'Book' Style gsub replacement code
StrTitlStyle = "<"..strTitlStyle..">" -- 'Title' Style gsub replacement code
StrHideStyle = StrLessLess.."%1"..StrMoreMore -- Hide style gsub replacement code
StrMatchPref = StrLessLess.."([ibcu])"..StrMoreMore.." ?" -- Match style prefix gsub code
StrMatchSuff = " ?"..StrLessLess.."(/[ibcu])"..StrMoreMore -- Match style suffix gsub code
StrReformat = "" -- Closing message when Reformat Metafields is used
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
-- Current selected GEDCOM destination globals:
IntMode = 1 -- Index into TblMode
StrFull = "+" -- "+" Full / "-" Brief mode
StrAbbr = "Std" -- Abbreviated mode name
StrMode = "Std+" -- Abbr .. "+" Full / "-" Brief mode for current rules
StrName = StrAbbr -- Same as StrAbbr but for choosing default modes (only needed for CEA/B)
StrNode = StrMode -- Same as StrMode but for choosing default rules (only needed for CEA/B)
IntNode = IntMode -- Same as IntMode but for choosing various modes (only needed for CEA/B)
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/Other Options tab; @ handling; -- V5.0 added Make="OFF/ON";
table.insert(TblMode, { Abbr="Std"; Full="Standard GEDCOM Release 5.5" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; 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" ; Make="OFF"; AtAt="@@"; }) -- V2.8 -- V4.0
table.insert(TblMode, { Abbr="AQP"; Full="Ancestral Quest Program" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.8
table.insert(TblMode, { Abbr="AGS"; Full="Ancestris Genealogy Software" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.8
table.insert(TblMode, { Abbr="ANC"; Full="Ancestry Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V2.2
table.insert(TblMode, { Abbr="BHG"; Full="Behold Genealogy" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V5.1
table.insert(TblMode, { Abbr="FH5"; Full="Family Historian V5.0" ; Tidy="OFF"; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; })
if IntFhVersion > 6 then
table.insert(TblMode, { Abbr="FH6"; Full="Family Historian V6.2" ; Tidy="OFF"; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V4.0
end
table.insert(TblMode, { Abbr="FST"; Full="Family Search Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.2
table.insert(TblMode, { Abbr="FTA"; Full="Family Tree Analyzer" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="FTD"; Full="Family Tree DNA" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="ON" ; AtAt= "@"; }) -- V3.3
table.insert(TblMode, { Abbr="FTL"; Full="Family Tree Maker (legacy)" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V4.0
table.insert(TblMode, { Abbr="FTM"; Full="Family Tree Maker 2019" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V4.0
table.insert(TblMode, { Abbr="FMP"; Full="FindMyPast Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V2.2
table.insert(TblMode, { Abbr="GKP"; Full="GEDKeeper Program" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; Text=10000; }) -- V6.0 -- V6.1 added Text limit where < 1,000,000
table.insert(TblMode, { Abbr="GWC"; Full="GEDmill Website Creator" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.3
table.insert(TblMode, { Abbr="GST"; Full="GedSite" ; Tidy="OFF"; Fact="ON" ; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.1
table.insert(TblMode, { Abbr="GSP"; Full="GedStar Pro for Android" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="GRT"; Full="Genes Reunited Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="GFT"; Full="Gramps Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="HER"; Full="Heredis by BSD Concept" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; 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" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="MFT"; Full="My Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V4.0
table.insert(TblMode, { Abbr="MYH"; Full="MyHeritage Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.2
table.insert(TblMode, { Abbr="RFT"; Full="RootsFinder Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.8 -- V4.0 "@@" => "@"
table.insert(TblMode, { Abbr="RMT"; Full="Roots Magic Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="RWW"; Full="RootsWeb WorldConnect" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="TNG"; Full="The Next Generation" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="TPT"; Full="TribalPages Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.1
table.insert(TblMode, { Abbr="ZPG"; Full="ZoomPast Genealogy" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V2.0
table.insert(TblMode, { Abbr="CEA"; Full="Custom Export Alpha GEDCOM 5.5" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="CEB"; Full="Custom Export Bravo GEDCOM 5.5.1"; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
for intMode, tblMode in ipairs(TblMode) do
TblMode[tblMode.Abbr] = intMode -- Reverse translate abbreviation to integer key -- V5.0
end
TblObje = { } -- Table of Media Object options
table.insert(TblObje, { Abbr="PART~LMO"; Full="Part Frames in Export Folder & Local Media Full File Paths"; Mode=" All " ; }) -- V1.8 revised options
table.insert(TblObje, { Abbr="FULL~LMO"; Full="Full Images in Export Folder & Local Media Full File Paths"; Mode=" " ; })
table.insert(TblObje, { Abbr= "ALL~LMO"; Full="Part & Full in Export Folder & Local Media Full File Paths"; Mode=" MYH " ; }) -- V3.2 MYH
table.insert(TblObje, { Abbr="PART~ABS"; Full="Part Frames in Export Folder & Media Record Full File Paths"; Mode=" " ; })
table.insert(TblObje, { Abbr="FULL~ABS"; Full="Full Images in Export Folder & Media Record Full File Paths"; Mode=" " ; })
table.insert(TblObje, { Abbr= "ALL~ABS"; Full="Part & Full in Export Folder & Media Record Full File Paths"; Mode=" FTL FTM GKP " ; }) -- V4.0 FTL -- V6.0 GKP
table.insert(TblObje, { Abbr="FILE~REL"; Full="Use Existing Media Files with Media Record Existing Paths" ; Mode=" FH5 FH6 " ; }) -- V4.0 FH6
table.insert(TblObje, { Abbr="FILE~ABS"; Full="Use Existing Media Files with Media Record Full File Paths"; Mode=" GST " ; }) -- V3.1
table.insert(TblObje, { Abbr="WIPE~ALL"; Full="Exclude all the Multimedia Files and Media Records 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
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 All " ; }) -- 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=" " ; })
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
table.insert(TblLabs, { Name="Principal" ; Help="Principal Witness Hyperlink" ; Tail="" ; }) -- V5.6 See below
table.insert(TblLabs, { Name="Family" ; Help="Principal Family Hyperlink" ; Tail="" ; }) -- V5.6 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
table.insert(TblLabs, { Name="Principal" ; Help="Principal Witness Hyperlink" ; Tail="" ; }) -- V5.6 See above
table.insert(TblLabs, { Name="Family" ; Help="Principal Family Hyperlink" ; Tail="" ; }) -- V5.6 See above
end
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="Original Date" ; Help="(DATE) Original Fact Date" ; }) -- V3.3 -- V5.5 moved
table.insert(TblLabs, { Name="Record Flag" ; Help="(_FLGS) Record Flag" ; }) -- V4.0 -- V5.5 moved
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="Footnote" ; Help="Footnote citation" ; }) -- V5.5
table.insert(TblLabs, { Name="Short Footnote" ; Help="Short Footnote citation" ; }) -- V5.5
table.insert(TblLabs, { Name="Bibliography" ; Help="Bibliography citation" ; }) -- V5.5
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
if IntFhVersion > 6 then
table.insert(TblLabs, { Name="Weblink" ; Help="Rich Text URL Weblink" ; Tail=": "; }) -- V5.3
table.insert(TblLabs, { Name="Record" ; Help="Rich Text Record Link" ; Tail=": "; }) -- V5.3
end
ResetResultSet()
end -- function PresetGlobalData
function StrGedcomExportMode(strAbbr,strFull) -- Handle GEDCOM Export mode and Full option -- V3.3 -- V5.0
-- strAbbr ~ Abbreviation for GEDCOM destination (optional)
-- strFull ~ "+" Full / "-" Brief mode selection (optional)
IntMode = TblMode[strAbbr] or TblOption.Mode -- Set global IntMode, StrAbbr, StrFull, StrMode, StrName, StrNode, IntNode
if type(IntMode) ~= "number" then
--? fhMessageBox("StrGedcomExportMode ~ IntMode = "..(IntMode or "nil"))
IntMode = TblMode[(IntMode or "Std")] -- Reverse translate abbreviation to integer key -- V5.0
end
StrAbbr = TblMode[IntMode].Abbr
StrFull = strFull or "+"
if not strFull and TblOption[StrAbbr] then
StrFull = TblOption[StrAbbr].Full or "+"
end
StrMode = StrAbbr..StrFull
StrName = TblOption[StrAbbr].Mode or StrAbbr
StrNode = StrName..StrFull
IntNode = TblMode[StrName]
end -- function StrGedcomExportMode
-- 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
-- Set per GEDCOM destination option setting -- -- V5.0
function SetPerModeOption(strOption,tblOption)
-- strOption ~ TblOption key: "Obje" , "Char" , "Priv" , "Rich"
-- tblOption ~ Options table: TblObje, TblChar, TblPriv, TblRich
for intOpt, tblOpt in ipairs ( tblOption ) do -- Search per Mode option settings -- V4.0
if tblOpt.Mode:match(" All ")
or tblOpt.Mode:match(" "..StrName.." ")
or tblOpt.Mode:matches(StrNode) then -- Option matches Mode of GEDCOM Export -- V4.0
TblOption[StrMode][strOption] = intOpt
if not tblOpt.Mode:match(" All ") then break end -- Break if matches Mode of GEDCOM Export
end
end
end -- function SetPerModeOption
-- Reset Options for Mode of GEDCOM Export --
function ResetGedcomOptions(tblMode,strFull,strNoToggles)
-- tblMode ~ TblMode entry for any GEDCOM destination
-- strFull ~ "+" Full or "-" Brief mode (optional)
-- strNoToggles ~ true inhibits toggle reset (optional)
local arrFull = { "+"; "-"; }
StrAbbr = tblMode.Abbr -- V3.3 -- V5.0
if strFull then arrFull = { strFull; } end
TblOption[StrAbbr] = TblOption[StrAbbr] or { } -- V5.0
TblOption[StrAbbr].Full = strFull or "+" -- V3.3
for _, strFull in ipairs ( arrFull ) do
StrGedcomExportMode(StrAbbr,strFull) -- V5.0
if not TblOption[StrMode] then TblOption[StrMode] = { } end -- V4.0
if not strNoToggles then -- Inhibit for Basic Options tab Reset Options button -- V4.0
local tblMode = TblMode[IntNode]
for _, strItem in ipairs ( ArrOptionToggles ) do
TblOption[StrMode][strItem] = tblMode[strItem] -- Default Tidy Names & Fact Set & Synth Prefix & Repeat Caption & Make DNA Birth/Death settings -- V3.3
end
end
SetPerModeOption("Obje",TblObje) -- Per Mode Media Conversion option setting -- V5.0
SetPerModeOption("Char",TblChar) -- Per Mode Character Encode option setting -- V5.0
SetPerModeOption("Priv",TblPriv) -- Per Mode [[private]] text option setting -- V3.2
SetPerModeOption("Rich",TblRich) -- Per Mode rich text format option setting -- V4.0
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 _, 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
-- 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
local strOptionPath = TblOption.Path
TblOption = { }
TblOption.Path = strOptionPath
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
StrGedcomExportMode() -- V3.3 -- V5.0
TblOption.Obje = TblOption[StrMode].Obje -- Default Media Object option setting
TblOption.Char = TblOption[StrMode].Char -- Default Character Encoding 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.Seps = TblOption.Seps or "OFF" -- Default folder path separator option -- V6.0
TblOption.Keep = TblOption.Keep or "OFF" -- Default keep Media subfolder option -- V2.9
TblOption.Proj = TblOption.Proj or "ON" -- Default for where settings are held -- V5.0
local strPath = general.SplitFilename(StrImport) -- Project GEDCOM file import folder path
StrExportOld = strPath:gsub("\\$","\\Export")
StrExportNew = StrExportOld -- V5.1
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.."\\"
StrExportNew = strPath:gsub("\\$","\\Export") -- Project default GEDCOM file export folder path -- V5.1
TblOption.Path = StrExportNew
else
TblOption.Path = TblOption.Path or StrExportNew -- Standalone GEDCOM defaults to subsidiary Export folder -- V5.1
end
end -- function ResetDefaultSettings
-- Convert textual abbreviation to droplist integer key -- -- V5.0
function ConvertAbbrToKey(strOption,tblOption)
-- strOption ~ TblOption key: "Mode" , "Obje" , "Char" , "Priv" , "Rich"
-- tblOption ~ Options table: TblMode, TblObje, TblChar, TblPriv, TblRich
local anyOption = TblOption[strOption]
if type(anyOption) == "string" then
for intOpt, tblOpt in ipairs ( tblOption ) do -- Convert textual abbreviation to integer key
if anyOption == tblOpt.Abbr then
anyOption = intOpt
break
end
end
if type(anyOption) == "string" then anyOption = 1 end -- Conversion failed so default to 1
end
TblOption[strOption] = anyOption
end -- function ConvertAbbrToKey
-- Load Sticky Settings from File --
function LoadSettings(strProject)
-- strProject ~ "ON" or "OFF" to set where settings are held
if not strProject then -- Only load GUI settings for initial case
iup_gui.LoadSettings("Bars") -- Includes "Main","Font","Bars" dialogues and "FontSet" & "History"
end
local strScope = "Project"
if strProject == "OFF" then -- V5.0
strScope = "Machine" -- V5.0
end
TblOption = iup_gui.LoadGlobal("Option",TblOption,strScope) -- Load local Project settings by default unless strProject is OFF
TblOption.Proj = strProject or TblOption.Proj or "ON"
if TblOption.Proj == "OFF" and strScope ~= "Machine" then
strScope = "Machine"
TblOption = iup_gui.LoadGlobal("Option",TblOption,strScope) -- V5.0
end
TblOption.Path = iup_gui.LoadFolder("Path",StrExportNew,strScope) -- 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","")),"MB_OK","MB_ICONEXCLAMATION")
end
if type(TblOption.Mode) == "string" then -- V5.0 Legacy fixes for Mode options
if TblOption.Mode == "GSW" then TblOption.Mode = "GST" end -- V3.1 Legacy Fix
if TblOption.Mode == "GSB" then TblOption.Mode = "GST" end -- V3.1 Lagacy Fix
if TblOption.Mode == "Sdr" then TblOption.Mode = "Str" end -- V4.0 Standard Release 5.5.1 Legacy 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
end
if type(TblOption.Priv) == "string" then -- Legacy fixes for Priv options
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
end
ConvertAbbrToKey("Mode",TblMode) -- Convert textual Mode option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Obje",TblObje) -- Convert textual Obje option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Char",TblChar) -- Convert textual Char option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Priv",TblPriv) -- Convert textual Priv option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Rich",TblRich) -- Convert textual Rich option from Abbr to integer key -- V5.0
local strHistory = iup_gui.History
if #strHistory > 2 then -- Perform legacy adjustments depending on historical version
if strHistory <= " 1.7 " then
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","")),"MB_OK","MB_ICONEXCLAMATION")
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 _, tblMode in ipairs ( TblMode ) do
ResetGedcomOptions(tblMode) -- Reset options for each Mode of GEDCOM Export (early legacy) -- V3.3
end
StrGedcomExportMode() -- V3.3 -- V5.0
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 _, tblMode in ipairs ( TblMode ) do -- Per Mode Media Object option setting
StrGedcomExportMode(tblMode.Abbr) -- V3.3 -- V5.0
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 _, 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 _, 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 _, 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 "+"
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] or {} -- V5.0
TblOption[strAbbr].Full = strFull
end
end
end
end
for _, tblMode in ipairs ( TblMode ) do -- Adjust old options to include new options
local strAbbr = tblMode.Abbr
TblOption[strAbbr] = TblOption[strAbbr] or {} -- V5.0
for _, strFull in ipairs ( { "+"; "-"; } ) do -- All abbreviated Modes and Full/Brief option -- V4.0
StrGedcomExportMode(strAbbr,strFull) -- V5.0
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
SetPerModeOption("Priv",TblPriv) -- Per Mode [[private]] text option setting -- V3.2
end
if not TblOption[StrMode].Rich then
SetPerModeOption("Rich",TblRich) -- Per Mode rich text format option setting -- V4.0
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
local tblMode = TblMode[IntNode] -- V5.0
if not TblOption[StrMode][strItem] then
TblOption[StrMode][strItem] = tblMode[strItem] -- Initialise Tidy Names & Fact Set & Synth Prefix & Repeat Caption & Make DNA Birth/Death 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
general.MakeFolder(TblOption.Path,iup_gui.MemoDialogue) -- Ensure that the Export folder exists -- V5.1
SaveSettings() -- Save sticky data settings
end -- function LoadSettings
-- Adjust Option integer key to Abbr string -- -- V5.0 -- Save key as abbreviation to cater for droplist changes plus defensive adjustments
function IntAdjustOption(strOption,tblOption)
-- strOption ~ TblOption key: "Mode" , "Obje" , "Char" , "Priv" , "Rich"
-- tblOption ~ Options table: TblMode, TblObje, TblChar, TblPriv, TblRich
ConvertAbbrToKey(strOption,tblOption) -- Convert erroneous textual abbreviation to integer key
local intOption = TblOption[strOption] or 1 -- Should be integer key of droplist option
if type(intOption) == "number" then
intOption = math.floor( math.max(intOption,1) ) -- Ensure integer is within range of droplist keys
intOption = math.floor( math.min(intOption,#tblOption) )
TblOption[strOption] = tblOption[intOption].Abbr -- Convert to abbreviation for save to cater for droplist changes
end
return intOption
end -- function IntAdjustOption
-- Save Sticky Settings to File --
function SaveSettings(strProject)
-- strProject ~ "ON" or "OFF" to set where settings are held
local intMode = IntAdjustOption("Mode",TblMode) -- Save the Mode option using Abbr rather than integer key -- V5.0
local intObje = IntAdjustOption("Obje",TblObje) -- Save the Obje option using Abbr rather than integer key -- V5.0
local intChar = IntAdjustOption("Char",TblChar) -- Save the Char option using Abbr rather than integer key -- V5.0 -- Eliminates non-BOM options
local intPriv = IntAdjustOption("Priv",TblPriv) -- Save the Priv option using Abbr rather than integer key -- V5.0
local intRich = IntAdjustOption("Rich",TblRich) -- Save the Rich option using Abbr rather than integer key -- V5.0
local strScope = "Project"
strProject = strProject or TblOption.Proj or "ON"
if strProject == "OFF" then -- V5.0
strScope = "Machine" -- V5.0
end
iup_gui.SaveGlobal("Option",TblOption,strScope) -- V5.0
iup_gui.SaveFolder("Path",TblOption.Path,strScope) -- V1.6
iup_gui.SaveSettings("Bars") -- Includes "Main","Font","Bars" dialogues and "FontSet" & "History"
TblOption.Mode = intMode
TblOption.Obje = intObje
TblOption.Char = intChar -- V5.0
TblOption.Priv = intPriv
TblOption.Rich = intRich
StrGedcomExportMode() -- Set IntMode, StrAbbr, StrMode, StrName, StrNode, IntNode, StrFull -- V5.0
StrZipFMP = TblOption.Path.."\\ZipFMP\\" -- FMP folder for ZIP bulk Media files -- V3.3
end -- function SaveSettings
function doCopyFile(strSource,strExport) -- Copy media file and modification date-time; used by doMediaError(), doMakeFile(), doFileLink(), doUniqueId(), ExportGedcom()
-- strSource ~ Source file path
-- strExport ~ Export file path
local isOk, strErr = pcall(general.CopyFile,strSource,strExport) -- V5.0 -- V5.2 -- Trap and report errors
if not isOk then
fhMessageBox("\nCopy File Error\n"..(strErr or "File not copied").."\nSource File:\n"..strSource.."\nExport File:\n"..strExport.."\n","MB_OK","MB_ICONEXCLAMATION")
fhSleep(1000,100)
end
end -- function doCopyFile
function HandleNonIntegerOption(strOption) -- Check and report non-integer options -- V5.0
-- strOption ~ TblOption key: "Obje" , "Char" , "Priv" , "Rich"
if type(TblOption[strOption]) ~= "number" then
--? fhMessageBox("SetSharedData() TblOption."..strOption.." = "..(TblOption[strOption] or "nil"),"MB_OK","MB_ICONEXCLAMATION") --?
TblOption[strOption] = 1
end
end -- function HandleNonIntegerOption
function SetSharedData() -- Shared variables for MakeRules(), setRules() & 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
StrGedcomExportMode() -- GEDCOM Export mode Mode = Std+, FH5+, FTM-, FMP-, etc, Abbr = Std, FH5, FTM, TNG, etc, Full = + or - -- V3.3
HandleNonIntegerOption("Obje")
HandleNonIntegerOption("Char") -- Defensive corrections needed? -- V5.0
HandleNonIntegerOption("Priv")
HandleNonIntegerOption("Rich")
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
strChar = TblChar[TblOption.Char].Abbr -- GEDCOM character encoding option ANSI, UTF8, UTF16, etc
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[IntNode].AtAt -- GEDCOM "@@" to single "@" or "§" or keep, as many programs do not honour GEDCOM "@@" convention, only for setRules() and doPrune() -- V3.3 -- V1.9 -- V5.0 IntNode instead of TblOption.Mode
intText = TblMode[IntNode].Text or 1000000 -- Set the long text field maximum character length -- V6.1
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 doUniqueId() -- 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"
and StrName ~= "FH5" and StrName ~= "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
isMake = ( TblOption[StrMode].Make == "ON" ) -- Make missing Birth/Death events for DNA products, for doUniqueId(), etc -- V5.0
arrMake = {} -- Table of facts with Normal Time Frame = Soon After Birth and Post_Death -- V5.0
intFontSize, strFontName = (iup_gui.GetRegKey("HKCU\\Software\\Calico Pie\\Family Historian\\2.0\\Preferences\\PDX Font") or ""):match("^(%d+),[%d,]+(.-)$") -- V5.7
intFontSize = ( intFontSize or 200 ) / 20 -- Default font size and name -- V4.5
end -- function SetSharedData
function MakeRules() -- Prototype for conversion Rules
SetSharedData() -- V4.0 -- V5.0 -- Use IntMode, StrAbbr, StrMode, StrName, StrNode, IntNode, StrFull below
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 Text from Source or 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 strFormat = nil -- Format of Media set by doMakeFile() due to TblOption.Jpeg == "ON" -- V5.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; -4 = String convert error; -5 = Extremely long text;
-- 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
if intRule == -5 then strItem = "Extremely Long Text Check" end -- V6.1
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
-- Adapt to use lookup table indexed by StrMode if other products need hyperlinks
local function strHyperlinkInd(strRecId) -- Create TNG hyperlink for Individual -- V5.6
return ''..strRecordName(strRecId)..''
end -- local function strHyperlinkInd
local function strHyperlinkFam(strRecId) -- Create TNG hyperlink for Family -- V5.6
local strFam = dicLabel["Family"] -- Translate 'Family' label -- V5.6
if #strFam == 0 then return "" end
return ''..strFam..' of '
--! return '..(strRecordName(strRecId):gsub("... of ",""))..'
end -- local function strHyperlinkFam
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).."..."
elseif strRecId:match("^[IF]")
and strTree and StrAbbr == "TNG" then -- Use hyperlink in TNG Witness Role notes -- V5.6
-- John SMITH
-- John SMITH and Jane DOE
if strRecId:match("^I") then
return strHyperlinkInd(strRecId) -- Individual hyperlink -- V5.6
end
if strRecId:match("^F") then
local strHref = strHyperlinkFam(strRecId) -- Family hyperlink with Husband & Wife hyperlinks -- V5.6
local strPref = ""
local ptrFam = fhNewItemPtr()
ptrFam:MoveToRecordById("FAM",tonumber(strRecId:match("%d+")))
for _, strTag in ipairs ({ "~.HUSB[1]>"; "~.WIFE[1]>"; "~.HUSB[2]>"; "~.WIFE[2]>"; }) do
local ptrTag = fhGetItemPtr(ptrFam,strTag) -- Cater for none, single, and same sex parents -- V5.6
if ptrTag:IsNotNull() then
strRecId = "I"..fhGetRecordId(ptrTag)
strHref = strHref..strPref..strHyperlinkInd(strRecId)
strPref = " & "
end
end
return strHref
end
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
if tblRecord[intLine] then
table.insert(tblRecord,intLine,strText) -- Insert text into record array
else
tblRecord[intLine] = strText -- Append text onto record array -- V5.3
end
if tblLineNo[intLine] then
table.insert(tblLineNo,intLine,intOrig) -- Insert import line number of current line -- V4.0
else
tblLineNo[intLine] = intOrig -- Append import line number of current line -- V5.3
end
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"; };
_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"; };
_UID = { Name="#) Unique Id:\t"; };
_FSID = { Name="#) FamilySearch Id:\t";}; -- V5.0
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 dicErr = {} -- List of inhibited error messages -- V4.5
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 intChar = 0 -- Characters in long root line -- V5.3
local arrLong = {} -- Accumulate a long root line -- V5.3
repeat
intLine = intLine + 1
local strLine = tblRecord[intLine] -- Get next line from record
local intLev, strTag, strVal = getParts(strLine)
intChar = intChar + #strVal
if intChar > (intText * 1.1) then strVal = "" end -- Ignore tail of excessively long text -- V5.3 -- V6.1
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
table.insert(arrLong,"\r"..strVal) -- Append CONTinuation text after return -- V5.3
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
table.insert(arrLong,strVal) -- Apend CONCatenation or _TEXT text -- V5.3
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
table.insert(arrLong,"\n_FMT") -- Apend _FMT tag -- V4.3 -- V5.3
else
if #arrLong > 1 then
tblRecord[intLine-1] = table.concat(arrLong) -- Update long root line
arrLong = {}
end
arrLong[1] = strLine -- Initial long text line -- V5.3
intChar = 0
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
local intChar = 0
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(" ")
or (strText:byte(intMax) or 0) > 127 do -- Avoid CONC break on space or UTF8 character -- V5.3
intMax = intMax - 1
end
if intMax <= 0 then intMax = 254 - #strTag - 2 end -- Cater for 250+ consecutive space/UTF8 chars -- 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
strText = strText:sub(intMax+1) -- Extract tail of long text
intLine = intLine + 1
if intLine % 23 == 0 then
fhSleep(10,8)
collectgarbage("step",0) -- Improves run time! -- V5.3
end
intChar = intChar + #strVal
if intChar > intText then strText = "" end -- Discard tail of long text -- V5.3 -- V6.1
until #strText == 0
if intChar > intText then -- Report very long text -- V5.3 -- V6.1
local strRid,strRec = tblRecord[1]:match("^0 @%u(%d+)@ ([_%u]+)")
local strType = "Note"
if strTag:match("TEXT") then strType = "Text From Source" end
local strText = tostring(intText):gsub("000$",",000"):gsub("000,",",000,") -- V6.1
local strErr = "Extremely long text" -- V6.1
local strMsg = "Extremely long "..strType.." exceeds "..strText.." characters.\n\nExcess "..strType.." text is discarded from the export.\n\nSee "..strRec.." Record Id "..strRid.." and correct the "..strType.."."
if not dicErr[strErr] then
local intKey = iup_gui.MemoDialogue(strMsg.."\n \n Inhibit all similar future messages? \n \n","Yes Inhibit","No Report")
if intKey == 1 then dicErr[strErr] = true end -- Inhibit error message -- V6.1
progbar.Focus()
end
tblRecord.Title = "Text > "..strText.." Chars"
strRec0 = tblRecord[1]
ptrRec0:MoveToRecordById(strRec,strRid)
setResultSet(0,-5,strLine,strMsg:gsub("\n+"," ")) -- Update the Result Set -- V6.1
break
end
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 StrName == "ANC" then -- V5.0 StrName instead of StrAbbr
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
local strWeblink = dicLabel["Weblink"]:gsub("%%","%%%%") -- V5.3
local strRecord = dicLabel["Record"] :gsub("%%","%%%%") -- V5.3
if strRich == "REMOVE" then -- Remove any rich text format codes -- V4.0
-- Web Link or
strVal = strVal:gsub('',strWeblink..'%1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 -- V5.3
strVal = strVal:gsub('',strWeblink..'%1 "%2"') -- V5.3
-- Link to Record or
strVal = strVal:gsub('',strRecord..'%2') -- V5.1 -- V5.3
-- >> Indents to tab
strVal = strVal:gsub('^>+','\t'):gsub('\r>+','\r\t')
-- bold italic underline strike subscript superscript etc deleted
strVal = strVal:gsub('?[biuschaftr].->','')
elseif strRich == "REDUCE" then -- Reduce rich text to basic GedStar Pro & RootsMagic format -- V4.0
-- Web Link or
strVal = strVal:gsub('',strWeblink..'%1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 -- V5.3
strVal = strVal:gsub('',strWeblink..'%1 "%2"') -- V5.3
-- Link to Record or
strVal = strVal:gsub('',strRecord..'%2') -- V5.1 -- V5.3
-- 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('?[chaftr][lsoa].->','') -- deleted
-- * Bullet
strVal = strVal:gsub('^%* ','\t'..StrBullet..' '):gsub('\r%* ','\r\t'..StrBullet..' ')
-- >> Indents to tab
strVal = strVal:gsub('^>+','\t'):gsub('\r>+','\r\t')
if StrName == "GSP" then -- Convert anchor to «?» so < and > are hidden -- V5.0 StrName instead of StrAbbr
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('',strWeblink..'%1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 -- V5.3
strVal = strVal:gsub('',strWeblink..'%1 "%2"') -- V5.3
-- Link to Record or
strVal = strVal:gsub('',strRecord..'%2') -- V5.1 -- V5.3
-- 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('?s>',''):gsub('?[chaftr][lsoa].->','') -- 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)
if not strAny then strAny = strVal end -- V6.4
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 ''..(strRow:gsub('|','
'):gsub('\r','
'))..'
' end) -- V4.3 -- Add
-- Alignment
strAny = strAny:gsub('%s-([^`]-)
', '%1 ') -- V4.3 -- Removed span
strAny = strAny:gsub('%s-([^`]-)
', '%1 ') -- V4.3 -- Removed span
strAny = strAny:gsub('%s-([^`]-)
', '%1 ') -- 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 or
strAny = strAny:gsub('', strRecord..'"%2"') -- V5.1 -- V5.3
-- * 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 = strAny
if intLev >= 0 then -- V6.4
strVal = tostring(intLev).." "..strTag.." "..strAny
end
end
elseif StrName == "GSP" and dicNameTag[strTag] and dicNameTag[strTag].A then -- and LFT?? -- V5.0 StrName instead of StrAbbr
strVal = strVal:gsub('<(.-)>','<%1>') -- Convert attribute value reserved chars < > to HTML escape codes
end
if StrFull == "-" then
strVal = strVal:gsub("[%z\01-\12\14-\31\127]"," ") -- In Brief mode replace invalid control chars with space for strict GEDCOM -- V5.0 \13 = \r must be kept, %z allowed in Lua 5.3
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
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
--[=[ Check if TextPart TIDY differs...
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","MB_OK","MB_ICONEXCLAMATION")
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
local strPre = "" -- V5.0
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, strFnd, strAny, isLink = getParts(strNext)
local strTag = intLev.." "..strFnd -- V5.0
if not isLink and -- Exclude any Record @link@ or UDF @@link@@ -- in V1.8 is this needed ???
( strTag == strNeed or ( strTag == strElse and strPre ~= "_FIELD" ) ) -- V5.0
or intLev < intEnd then
return intNext, strTag -- Return line and tag found
end
strPre = strFnd -- Ensure TEXT not preceded by _FIELD -- V5.0
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( doPrune(strCont) ) -- Handle rich text NOTE, TEXT, etc -- V3.2 -- V5.0
if intLev < intGet then break end -- Escape when no more CONC/CONT/subsidiary tag
if strTag == "_FMT" then
strCont = intPut.." CONT " -- V5.2
elseif 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 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 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
elseif strTag == "PLAC" and strVal:match("%[unknown record%]$") then
break -- Omit synthetic Place citation Note -- V5.2
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 -- V5.6 -- Significant changes for 'Move to Fact Note Min' rule and Labels Set A new labels
-- strWitness ~ Witness link or name
local intLine = tblRecord.Line
local strName = "" -- Witness name
local strWitId = strWitness:match("@([IF]%d+)@") -- [IF] added to cater for Principal Family in synthetic Fact Role -- V2.0
if tblRecord[intLine+1] == "3 ROLE ~Principal~" then -- Handle synthetic Fact Witness Fact -- V5.6
for intType = intLine-1, 1, -1 do
local strType = tblRecord[intType]
local intLev, strTag, strVal = getParts(strType)
if intLev <= 1 then break end -- Quit at start of fact
if strTag == "TYPE" then -- Synthetic fact type found
for intHead = 1, intLine-1 do -- Obtain the RecId of the current record Individual
local strRecId = tblRecord[intHead]:match("^0 @(I%d+)@")
if strRecId then
local dicSHAR = dicRule["2 _SHAR"] or {} -- 'Move to Fact Note Min' rule? -- V5.6
local isOptMin = dicSHAR.Item:match("Move to Fact Note Min")
local strWitId = ""
local intNext = intLine + 2
while intNext < #tblRecord do -- Search through Witnesses after the Principal
local intLev, strTag, strVal = getParts(tblRecord[intNext])
if strTag:match("_SHA[NR]") then -- Name Only or Individual Witness
strWitId = strVal:match("@(I%d+)@") or ""
local intLev, strTag, strVal = getParts(tblRecord[intNext+1])
if strTag == "ROLE" and strRecId == strWitId then
strType = strType.." and "..strVal -- Current record Individual is a Witness so update fact Type with Role
end
if isOptMin then -- 'Move to Fact Note Min' rule so delete _SHA[NR] subsidiary lines -- V5.6
doRemoveAll("2",intNext)
doDelete(intNext)
intNext = intNext - 1
end
end
if intLev <= 1 then break end -- Quit at end of fact
intNext = intNext + 1
end
tblRecord[intType] = strType:gsub("Role Unknown and ","") -- Save updated fact type -- V5.6
break
end
end
break
end
end
end
if strWitId then -- Lookup Witness Individual record name
strName = strRecordText(strWitId) -- Return [RecId] Name or TNG hyperlink -- V5.6 -- V2.2
else
strName = "Name:\t "..strWitness -- Name Only Witness name
end
local arrNote = {} -- Move all the Fact Witness details to local Note -- V5.6
local arrSour = {}
local strRole = "Unknown:\t "
local intNext = intLine+1
while intNext <= #tblRecord do -- Search subsequent lines for Witness/Role/Source/Note
local strNext = tblRecord[intNext]
local intLev, strTag, strVal = getParts(strNext)
if intLev <= 2 and not strTag:match("SHA[RN]") then break end -- Quit when all Fact Witness tags done -- V5.6
if strTag == "SOUR" or intLev > 3 then -- Save all Source Citation lines -- V1.9
if strGedExport == "5.5.1" then
tblRecord[intNext] = strNext:gsub("^(%d)",tostring(intLev-1)) -- Move Source Citation up to Fact -- V5.6
intNext = intNext + 1
else
strNext = doPrune(strNext) -- Handle any Rich Text format Notes -- V6.3
table.insert(arrSour,"\n"..strNext)
doDelete(intNext) -- Delete the Source Citation line -- V1.8
end
elseif strTag:match("_SHA[RN]") then -- Handle allsubsequent Fact Witnesses -- V5.6
if strRole then
table.insert(arrNote,strRole..strName) -- Move previous Witness Role to Fact Note
end
local strWitId = strVal:match("@(I%d+)@") -- Lookup Witness Individual record name
if strWitId then
strName = strRecordText(strWitId) -- Return [RecId] Name or TNG hyperlink -- V5.6
else
strName = "Name:\t "..strVal -- Name Only Witness name
end
strRole = "Unknown:\t "
doDelete(intNext) -- Delete Fact Witness line
elseif strTag == "ROLE" then -- Save the Role
strRole = strVal..":\t "
if strVal == "~Principal~" then
strRole = dicLabel["Principal"] -- Translate 'Principal' label -- V5.6
if #strRole > 0 then
strRole = strRole..":\t "
end
end
table.insert(arrNote,strRole..strName) -- Move the Witness Role & Name to Fact Note
strRole = nil
doDelete(intNext) -- Delete Role line -- V1.8
elseif strTag == "NOTE" then
strVal = doPrune(strVal) -- Handle any Rich Text format Notes -- V6.3 -- V5.2
table.insert(arrNote,strVal) -- Move Witness Note to Fact Note
doDelete(intNext) -- Delete Note line -- V1.8
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
table.insert(arrNote,strRole..strName) -- Move last Witness Role to Fact Note
end
if #arrSour > 0 then
arrNote[#arrNote] = arrNote[#arrNote]..table.concat(arrSour) -- Append Source Citations to Fact Note
end
if dicWhere["Witness Role"] == StrBefore then
for intNote = #arrNote, 1, -1 do -- Reverse Fact Witness Note entries -- V5.6
doFactNote(arrNote[intNote])
end
else
for intNote = 1, #arrNote do -- Forward Fact Witness Note entries -- V5.6
doFactNote(arrNote[intNote])
end
end
return "" -- Remove original 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 doAddrTidy(strLev,strAddr) -- Tidy Address field -- V2.3
-- strLev ~ Level of ADDR line
-- strAddr ~ Address of ADDR line
return strLev.." ADDR "..strTidyText(strAddr) -- V5.0
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[StrName] -- V3.3 -- V5.0 StrName instead of StrAbbr
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
strMsid = tblGedSelect[strGedImport].Msid -- Media sequence id tag for FH V6 = _ASID and for FH V7 = _SEQ -- V4.0
strFact = tblGedSelect[strGedExport].Fact -- Custom fact tag for GEDCOM 5.5 = 1 EVEN and GEDCOM 5.5.1 = 1 FACT -- V4.0
if StrName == "MYH" then strFact = "1 EVEN" end -- MYH uses 5.4.1 but EVEN tag -- V6.0
return "2 VERS "..strGedExport -- Output Gedcom version
end
return nil -- Retain original line
end -- local function doVersion
function CopyBranch(ptrSource,ptrTarget) -- Copy subsidiary branch of record -- V5.5
local strTag = fhGetTag(ptrSource)
if strTag == "_FMT" then return end -- Skip rich text format code
if strTag == "_FIELD" then -- Substitute metafield shortcut
strTag = fhGetMetafieldShortcut(ptrSource)
end
local ptrNew = fhCreateItem(strTag,ptrTarget,true)
if ptrNew:IsNull() then return end -- Escape if item not created
if strTag == "_SRCT" then
fhSetValueAsLink(ptrNew,dicSour.SRCT) -- Special case to prevent Source Template Update date/time changing
else
fhSetValue_Copy(ptrNew,ptrSource)
end
CopyChildren(ptrSource,ptrNew)
end -- function CopyBranch
function CopyChildren(ptrSource,ptrTarget) -- Copy child branches of record -- V5.5
local ptrFrom = ptrSource:Clone()
ptrFrom:MoveToFirstChildItem(ptrFrom)
while ptrFrom:IsNotNull() do
CopyBranch(ptrFrom,ptrTarget)
ptrFrom:MoveNext()
end
end -- function CopyChildren
function DeleteChildren(ptrSource) -- Delete child branches of record -- V5.5
local ptrFrom = ptrSource:Clone()
ptrFrom:MoveToFirstChildItem(ptrFrom)
while ptrFrom:IsNotNull() do
local ptrItem = ptrFrom:Clone()
ptrFrom:MoveNext()
fhDeleteItem(ptrItem)
end
end -- function DeleteChildren
local function doReform(strTag,strLab) -- Convert a Format Template to formatted text -- V5.5
-- strTag ~ Source Template Format tag ~.FOOT, ~.SHRT, ~.BIBL
-- strLab ~ Note text label
local strForm = fhGetValueAsText(fhGetItemPtr(dicSour.SRCT,strTag)) -- Format Template
strForm = strForm:gsub("{([_%w]-):BOOK}", StrBookStyle) -- Replace 'Book' & 'Title' styles with style codes -- V5.3
strForm = strForm:gsub("{([_%w]-):TITLE}",StrTitlStyle)
strForm = doPrune("0 X "..strForm.."\n_FMT"):gsub("^0 X ","") -- Honour rich text option
strForm = strForm:gsub("<(/?[ibcu])>",StrHideStyle) -- Hide style codes temporarily as Title Format disallows them -- V5.3
fhSetValueAsText(fhGetItemPtr(dicSour.SRCT,"~.TITL"),strForm)
fhSrcEnableAutoTitle(dicSour.SOUR,true) -- Obtain the formatted text in Title except for Citation-specfic Metafields
strForm = fhGetValueAsText(fhGetItemPtr(dicSour.SOUR,"~.TITL"))
strForm = strForm:gsub(StrMatchPref,"<%1>") -- Reveal style codes -- V5.3
strForm = strForm:gsub(StrMatchSuff,"<%1>")
strForm = strForm:gsub("(.-) ",string.upper) -- Convert capitals -- V5.3
if #dicLabel[strLab] > 0 then
tblRecord.Arg = strLab
addNoteText(strForm,"1 NOTE","1 CHAN") -- Insert formatted text into 1 CONT Record Note but only if Note text Label is defined -- V5.7
end
return strForm
end -- local function doReform
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
local strFoot = ""
local intLine = tblRecord.Line + 1
if strRef then
if not dicSour.SOUR then -- Create dummy Source record and Source Template record -- V5.5
dicSour.SOUR = fhCreateItem("SOUR")
dicSour.SRCT = fhCreateItem("_SRCT") -- These increase Record Id unless Edit > Undo Plugin Updates is used!
fhUpdateDisplay()
end
local strRid = tblRecord[1]:match("0 @S(%d+)@ SOUR") -- Get pointer to actual Project Source record
local ptrSour = fhNewItemPtr()
ptrSour:MoveToRecordById("SOUR",tonumber(strRid))
local ptrSrct = fhGetItemPtr(ptrSour,"~._SRCT>") -- Get pointer to actual Project Source Template
DeleteChildren(dicSour.SRCT)
DeleteChildren(dicSour.SOUR)
CopyChildren(ptrSrct,dicSour.SRCT) -- Copy actual records to dummy records
CopyChildren(ptrSour,dicSour.SOUR)
fhUpdateDisplay()
strFoot = doReform("~.FOOT","Footnote") -- Convert Footnote Format to formatted text Note
doReform("~.SHRT","Short Footnote") -- Convert Short Footnote Format to formatted text Note
doReform("~.BIBL","Bibliography") -- Convert Bibliography Format to formatted text Note
StrReformat = "Use Edit > Undo Plugin Updates (Ctrl+Z) to avoid increased Source Record Id." -- V5.5
end
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 ) -- V5.5
doDelete(intLine)
elseif intLev == 2 then -- Convert template Field value line
if strTag == "REPO" then
doInsert("1 REPO "..strVal,intLine) -- Use generic Repository link even with Remove entirely
intLine = intLine + 1
strVal = strRecordName(strVal:match("@(R%d+)@")) -- Translate link to Repository name for add to Note -- V4.5
elseif strTag:match("^_") then
break
end
if strRef then -- Convert template field to Note -- V5.2
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
else
doRemoveAll("1",intLine-1) -- Remove template lines entirely -- V5.2
end
elseif strRef == "Reformat" then
if intLev == 1 then
if strTag == "TITL" then -- Reformat TITL and ABBR fields
if StrName ~= "GST" -- V5.7
and StrName ~= "RMT"
and StrName ~= "TNG" then -- V5.5
strFoot = strFoot:gsub("<(/?[ibu])>","") -- Unless GST/RMT/TNG only support Capitals & Single/Double Quotes -- V5.3
end
doModify("1 TITL "..strFoot,intLine) -- Modify the 1 TITL to hold Footnote text
intLine = intLine + 1
local intLev, strTag, strOld = getParts(tblRecord[intLine])
if intLev == 1 and strTag == "ABBR" then -- Move Title value to Short Title
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
if strRef and #dicLabel["Source Template"] > 0 then -- Convert Source Template link to a Note Record link -- V5.2 -- V5.7
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
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("^Convert to Notes")
or strRule:match("^Footnote to Title") then -- Convert Metafield into Citation local Note -- V5.2 -- V5.7
-- Use Footnote in "Footnote to Title" mode if it includes a Citation _FIELD and add new option to omit _FIELD values e.g. "Footnote Only" ???
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") or #dicLabel["Source Template"] == 0 then
return doRemoveAll("0") -- Remove record completely -- V5.7
end
if strRule:match("^Convert to Notes")
or strRule:match("^Footnote to Title") then -- V5.2 -- V5.7
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 %a+") then -- V6.0
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
local strTitl = strRule:match("^Move to (.+)") -- V6.0 for GKP
local strDate = ""
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)
strTitl = strLine:match("Title: ([^\r]*)") or strTitl -- V6.0 for GKP
strDate = strLine:match("Date: ([^\r]*)") or strDate
end
if strRule:match("^Move to Task Record") then -- V6.0 for GKP
local strTask = strNote:gsub("N","TK")
doInsert("0 "..strTask.." _TASK") -- Create synthetic Task record
doInsert("1 NOTE "..strNote)
doInsert("1 _STARTDATE "..strDate)
doInsert("1 _GOAL General Research")
end
if strRule:match("^Move to Researches") then -- V6.0 for GKP
local strRsch = strNote:gsub("N","RS")
doInsert("0 "..strRsch.." _RESEARCH") -- Create synthetic Research record
doInsert("1 NOTE "..strNote)
doInsert("1 NAME "..strTitl)
doInsert("1 _STARTDATE "..strDate)
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 doLocation(strRid,strPlac) -- GKP Convert Place Record to Location Record -- V6.0
-- strRid ~ Whole record id
-- strPlac ~ Place tag & name
strRid = strRid:gsub("P","L")
strPlac = strPlac:gsub("_PLAC ","")
for intLine = 2, #tblRecord do -- Search through record
local intLev, strTag, strVal = getParts(tblRecord[intLine])
if strTag == "STAN" then -- STANdardized to Note
doRecNote(doNameTag(strTag).Name:gsub("^#%)","")..strVal,intLine)
break
end
end
doInsert("1 NAME "..strPlac,2) -- Add place name
return "0 "..strRid.."_LOC"
end -- local function doLocation
local function doBlankFact(strTag,strVal) -- Blank Fact needs a local Note for Ancestry -- V5.9
-- strTag ~ Tag for current line
-- strVal ~ Value of current line
if isRecord("INDI, FAM") then
local dicTag = dicNameTag[strTag] or {}
strVal = strVal or ""
if #strVal > 1 then -- Attribute has a value
for intLine = tblRecord.Line + 1, #tblRecord do
local intL, strT, strV = getParts(tblRecord[intLine])
if intL == 1 then
break
elseif intL == 2 and strT == "NOTE" then
tblRecord[intLine] = "2 NOTE \r"..strV
break
end
end
elseif dicTag.E or dicTag.A or dicTag.L then -- Event or Attribute or LDS Ordinance with no Attribute value
local intLine = tblRecord.Line + 1
local intL, strT, strV = getParts(tblRecord[intLine])
if intL == 2 and strT == "TYPE" then -- Skip custom Fact Type
intLine = intLine + 1
intL, strT, strV = getParts(tblRecord[intLine])
end
if intL == 1 or ( intL == 2 and strT == "SOUR" ) then -- No level 2 Date, Place, Note, etc, except Source
doInsert("2 NOTE No recorded Date/Place/etc.", intLine) -- Insert local Note so Ancestry does not exclude the Fact
end
end
end
return nil -- Retain original line
end -- local function doBlankFact
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
if dicRule["1 AZ"] then doBlankFact(strTag,strVal) end -- Ancestry any blank Fact check -- V5.9
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
if dicRule["1 AZ"] then doBlankFact("FACT",strVal) end -- Ancestry any blank Fact check -- V5.9
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 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 function doNote2Fact(strVal) -- Convert Individual (ANC) or Family Record Note to synthetic Fact Note -- V2.2 -- V3.3 -- V5.9
-- strVal ~ Value of NOTE line
local strArg = tblRecord.Arg or "Record Note" -- V5.9
if isRecord("INDI") and strArg == "Record Note"
or 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 "..strPrefix..strArg,intLine) -- V3.3 -- V5.9
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 StrNode ~= "GST" then -- V5.0 -- StrNode instead of StrMode
return doNoteLink("1",strVal)
end
return nil -- Retain original line
end -- local function doNote2Fact
local dicCiteNote = {FILE=" Object";FORM=" Object";TITL=" Object";OBJE=" Object";FAMC=" Parent";FAMS=" Spouse";}
local function doCiteNote(strLev,strVal) -- Convert Individual/Family/Fact long 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
if #strVal < 256 then
if intLev == 1 then return doNote2Fact(strVal) else return nil end -- V5.9
end
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
doInsert("2 NOTE Record Note", intLine) -- V5.9
intLine = intLine + 1
end
if intLev == 2 and strTag == "TYPE" then
doInsert("2 NOTE Fact Note", intLine) -- V5.9
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 isCitation(intLine,intLev) -- Check if part of a citation -- V5.3
for intPrev = intLine-1, 2, -1 do
local intPre, strTag = getParts(tblRecord[intPrev])
if intPre == intLev-1 then -- Find parent SOUR citation
return ( strTag == "SOUR" )
end
end
return false
end -- local function isCitation
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%-%._~:/%?#@!%$&\'\\%(%)%*%+,;=%%]+)')
local strUrl = 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
if isCitation(intLine,intLev) then
doInsert(intLev.." _LINK "..strUrl,intLine) -- Found parent SOUR citation so add _LINK tag with URL -- V4.6 -- V5.3
end
end
return nil -- Retain original line
end -- local function doWeblink
local function doWebTags(strLev,strVal) -- For source/citation local Note with Weblink URL add _WEBTAG URL for RMT -- V5.3
-- strLev ~ Level of current line
-- strVal ~ Value of current line
local intLev = tonumber(strLev)
local intLine = tblRecord.Line
if isRecord("SOUR")
or ( intLev > 1 and isCitation(intLine,intLev) ) then -- Source/citation so add _WEBTAG tag with URL -- V5.3
for strUrl in strVal:gmatch('([hf]t?tps?://[A-Za-z0-9%-%._~:/%?#@!%$&\'\\%(%)%*%+,;=%%]+)') do
doInsert(intLev.." _WEBTAG",intLine)
local intLev = intLev + 1
intLine = intLine + 1
doInsert(intLev.." NAME WebTag",intLine)
intLine = intLine + 1
doInsert(intLev.." URL "..strUrl,intLine)
intLine = intLine + 1
end
end
return nil -- Retain original line
end -- local function doWebTags
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("^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
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
elseif strForm == "Quarters" then -- Convert BET/AND quarter months to Q1-4 yyyy -- V5.1
local strYear1, strYear2 = strText:match("^2 DATE BET %u%u%u (%d%d%d%d) AND %u%u%u (%d%d%d%d)$")
if strYear1 and strYear2 and strYear1 == strYear2 then
strText = strText:gsub("BET JAN (%d+) AND MAR (%d+)","Q1 %1")
strText = strText:gsub("BET APR (%d+) AND JUN (%d+)","Q2 %1")
strText = strText:gsub("BET JUL (%d+) AND SEP (%d+)","Q3 %1")
strText = strText:gsub("BET OCT (%d+) AND DEC (%d+)","Q4 %1")
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","MB_OK","MB_ICONEXCLAMATION")
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 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
local _, strFile = general.SplitFilename(putFile)
local strMsg = strErr..strSize.."\n\nMultimedia Record Id: OBJE ["..strRid.."]"..strSid..strArea.."\n\nSource: "..getFile.."\n\nExport: "..strFile..strCopy
strErr = strErr:sub(1,15) -- Use first 15 chars to inhibit similar messages -- V5.9
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 -- crashed 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,getAnsi,getWasAnsi,putFile,putAnsi,putWasAnsi,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()
-- getAnsi ~ Ansi path of source media file
-- getWasAnsi~ true if getFile was Ansi compatible
-- putFile ~ Full path of target media file
-- putAnsi ~ Ansi path of target media file
-- putWasAnsi~ true if putFile was Ansi compatible
-- 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 oldType,oldT,oldL,oldB,oldR = putType,intT,intL,intB,intR -- Keep original parameters in case of error -- V3.7
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(getAnsi) -- Load source image from file and check for errors -- V5.0 getFile > getAnsi
if not getWasAnsi then general.DeleteFile(getAnsi) end -- V5.0
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(putAnsi,outType,putImage) -- Save image file with format type matching file type -- V3.7 -- V5.0 putFile > putAnsi
if not putWasAnsi then general.MoveFile(putAnsi,putFile) end -- V5.0
else
intErr = im.ERR_DATA -- GetOpenGLData failure detected -- V4.0
end
getImage:Destroy()
putImage:Destroy() -- Release image memory
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,getAnsi,getWasAnsi,putFile,putAnsi,putWasAnsi,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,strFile) -- Update the Result Set arrays -- V3.7 -- V4.5 -- V5.0
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
-- 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 -- V6.3 [\\/] to cater for \ & / delimiter
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
putFile = putPath..putFile -- Allow Part Frame Folders -- V4.0
end
if TblOption.Seps == "ON" then
putFile = putFile:gsub("\\","/") -- Conditional MacOS/Unix style / path separators -- V6.0
end
strFormat = nil
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"
strFormat = "jpeg" -- V5.3
end
end
if general.FlgFileExists(getFile) then -- Source media file exists
local isOK,getAnsi,getWasAnsi = pcall(general.FileNameToANSI,getFile,"Export Gedcom File Source.jpg") -- V5.2
if not isOK then -- Report string conversion failed -- V5.2
local strErr = getAnsi:gsub('%[string ".-"%]:%d+: ','') -- [string "C:\ProgramData\Calico Pie\Family Historian\Pl..."]:6832: fhConvertUTF8toANSI - String conversion failed
dicErr[strErr] = (dicErr[strErr] or 0) + 1
if dicErr[strErr] <= 99 then
setResultSet(0,-4,strErr,getFile) -- Update the Result Set for fhConvertUTF8toANSI - String conversion failed
end
return putFile
end
if not getWasAnsi then doCopyFile(getFile,getAnsi) end -- V5.0 -- V5.2
local putAnsi,putWasAnsi = general.FileNameToANSI(putFile,"Export Gedcom File Target."..putType) -- V5.0
if not putWasAnsi and general.FlgFileExists(putFile) then doCopyFile(putFile,putAnsi) end -- V5.0 -- V5.2
if not general.FlgFileExists(putFile) -- No target file, or modification date-times differ
or lfs.attributes(getAnsi,"modification") > lfs.attributes(putAnsi,"modification") then -- V5.0 get/putFile > get/putAnsi
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
general.MakeFolder(general.GetParentFolder(putFile)) -- V5.0
putFile = doProcessImage(getFile,getAnsi,getWasAnsi,putFile,putAnsi,putWasAnsi,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
if not putWasAnsi then general.DeleteFile(putAnsi) end -- V5.0
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
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
local intLine = 1
while intLine < #arrRecord do -- V6.2
intLine = intLine + 1
local strLine = doPrune(arrRecord[intLine]) -- V1.9
local intLev, strTag, strVal = getParts(strLine) -- V2.0 intLev & getParts
if strTag:match("_NOT[AE]") then
local is_Note = dicRule[" _NOTA"] or dicRule[" _NOTE"] or {} -- Remove annotation notes? -- V6.2
if is_Note.Item:match("^Remove entirely") then
intLine = doDelete(intLine)
strLine = doPrune(arrRecord[intLine]) -- V1.9 Move on to next line
intLev, strTag, strVal = getParts(strLine) -- V2.0 intLev & getParts
end
end
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 StrName:match("GST") then -- V5.0 StrName instead of StrAbbr
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 = strFormat or strVal -- Use adjusted file format where necessary -- V5.3
strVal = strVal:gsub("^.+$",tblGedSelect[strGedExport].Type)
isNew = isNew or ( strOld ~= strVal )
strFormat = nil
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"
or strLine == "3 FILE " then -- V5.0
return doRemoveAll(strLev) -- Remove LMO Sort Date or missing File Link media -- V3.5
end
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 StrName:match("FH[56]") then -- V5.0 StrName instead of StrAbbr
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 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
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)
local isCapt = false -- Caption Note? -- V6.2
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
isCapt = true -- V6.2
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
if isCapt then -- Special case of Caption Note -- V6.2
tblRecord.Arg = "Caption Note"
doLocNote(tostring(intLev),strVal) -- Insert LMO Caption Note -- V6.2
tblRecord.Arg = dicArg
else
doInsert(strNew,intLine) -- Add new Local Media Object tag line
intLine = intLine + 1
end
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" or strMode == "CROP" then -- All Local Media Objects and ABS Media Links -- V3.2 -- V6.2
tblRecord[strPref] = true -- GKP & TNG add _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
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("{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
if strMode == "CROP" and not isNotFull then -- V6.2
local intLev = tonumber(strLev)
if tblObje.ASID._AREA then -- TNG adds CROP, TOP, LEFT, HEIGHT, WIDTH tags for _AREA framed full image -- V6.2
local strArea = tblObje.ASID._AREA[1].Val or "{}"
local tblArea = strArea:match("{(.*)}"):splitnumbers() -- Image frame _AREA co-ordinates {Top,Left,Bottom,Right} -- V1.4
local intT = tblArea[1] -- Top
local intL = tblArea[2] -- Left
local intH = tblArea[3] - intT -- Bottom - Top => Height
local intW = tblArea[4] - intL -- Right - Left => Width
local strLev = tostring(intLev+1)
doInsert(strLev.." CROP",intLnk)
intLine = intLine + 1
strLev = tostring(intLev+2)
doInsert(strLev.." TOP "..intT,intLnk+1)
intLine = intLine + 1
doInsert(strLev.." LEFT "..intL,intLnk+2)
intLine = intLine + 1
doInsert(strLev.." HEIGHT "..intH,intLnk+3)
intLine = intLine + 1
doInsert(strLev.." WIDTH "..intW,intLnk+4)
intLine = intLine + 1
end
if tblObje.ASID.NOTE and not isLocObje then
local strNote = tblObje.ASID.NOTE[1].Val or "" -- TNG adds local caption Note to framed full image -- V6.2
local dicArg = tblRecord.Arg
local strArg = dicArg.Lab
local strLab = dicLabel[strArg] or strArg..":\t" -- Label for Caption Note -- V3.3
local strLev = tostring(intLev+1)
doInsert(strLev.." NOTE "..strLab..strNote,intLnk)
intLine = intLine + 1
tblObje.ASID.NOTE = nil
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 isCROP = not isLocObje and tblObje and (tblObje.ASID._AREA or tblObje.ASID.NOTE)
local isUseFull = not( strMode == "CROP" and isCROP ) -- Use full frame unless CROP of AREA/NOTE needed -- V3.3 -- V6.2
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
setObjectUsed(strRiD) -- Use the Sid=0 & Rid key to set Media Object details as Used -- V6.2
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 = strFile -- V5.0
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()) 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("^[Mm]edia[\\/]","Media\\") -- Ensure Media folder name capitalised -- V4.0 -- V6.3 [\\/] to cater for \ & / delimiter
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 -- V6.3 [\\/] to cater for \ & / delimiter
-- Convert .tif into .jpg for GedSite? Not needed, but it was contemplated to go here. --
else
local getType = "~"
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
if TblOption.Seps == "ON" then
getFile = getFile:gsub("\\","/") -- Conditional MacOS/Unix style / path separators -- V6.0
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 ""
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
strKeys = strKeys:gsub("^[, ]+",""):gsub("[, ]+$",""):gsub("[, ]+",", ") -- Remove redundant commas -- V5.2
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 _, 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 doMakeEvent(strInit,strRid,arrArg) -- Make Birth/Death from 1st dated fact with matching Timeframe -- V5.0
-- strInit ~ Record Id initial letter ~ Not used
-- strRid ~ Record Id numerical digits ~ Not used
-- arrArg ~ List of Timeframes SOON-AFTER-BIRTH &/or POST-DEATH
local intMake = nil -- Line at which to make Birth/Death event
local strMade = nil -- Names of BIRT/DEAT events made
for _, strTime in ipairs (arrArg) do -- Loop through Timeframes
local strFact = strTime:match("%-(%u%u%uT)H$") -- Extract event tag BIRT/DEAT
local intLine = ( intMake or tblRecord.Line ) + 1 -- Line to resume or start search from
local strPref = nil -- Prefix EVEN- or _ATTR- for custom fact
local strYear = nil -- Date year from matching Timeframe fact
local strPart = nil -- Place part from matching Timeframe fact
intMake = nil -- Line at which to make Birth/Death event
repeat
local intLev, strTag, strVal = getParts(tblRecord[intLine]) -- Search record for facts with matching Timeframe
if intLev == 1 then
strPref = nil
if arrMake[strTag] == strTime then -- Standard fact matches Timeframe
intMake = intMake or intLine -- Note line to make new Birth/Death event
elseif strTag == "EVEN" then
strPref = "EVEN-" -- Cater for custom events
elseif strTag == "_ATTR" or strTag == "FACT" then
strPref = "_ATTR-" -- Cater for custom attributes
end
elseif intLev == 2 then
if strTag == "TYPE" and strPref then
if arrMake[strPref..strTag] == strTime then -- Custom fact matches Timeframe
intMake = intMake or intLine -- Note line to make new Birth/Death event
end
elseif strTag == "DATE" and intMake then
strYear = strVal:match("%d%d%d%d") -- Extract year from Date
local intLev, strTag, strVal = getParts(tblRecord[intLine+1])
if intLev == 2 and strTag == "PLAC" then
strPart = strVal:match(", *([^,]-)$") -- Extract last part from Place
end
end
strPref = nil
end
intLine = intLine + 1
until ( intMake and strYear ) or intLine > #tblRecord
if intMake and strYear then -- Make Birth/Death event with year as Date and possibly last Place part
doInsert("1 "..strFact,intMake)
intMake = intMake + 1
doInsert("2 DATE ABT "..strYear,intMake)
if strPart then
intMake = intMake + 1
doInsert("2 PLAC "..strPart,intMake)
end
intMake = intMake + 2 -- Skip fact that instigated this event
strMade = strMade or " DNA"
strMade = strMade.." "..strFact
end
end
return strMade -- Return name of synthetic BIRT/DEAT event(s) created if any
end -- local function doMakeEvent
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.."@"
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
else
fhMessageBox("\n Record mismatches generic rule. \n","MB_OK","MB_ICONEXCLAMATION") -- 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,anyArg) -- 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
-- anyArg ~ Number or text 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],anyArg) -- Add the Note/Source/Fact Id or Timeframe 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 for Tidy Place Record Name Conflicts
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 &/or synthesise DNA Birth/Death Events -- V2.0
-- tblRecord ~ Array of text lines in GEDCOM record
-- arrLineNo ~ Array of their import line numbers
-- strRid ~ Record Id of Principal Individual/Family
local strSHAR = "_SHAR" -- V5.6
local dicSHAR = dicRule["2 _SHAR"]
if IntFhVersion == 5 or not dicSHAR.Arg then -- Except if the Witnessed Facts are being kept or removed -- V3.8
if not isMake then return end -- Synthesise DNA Birth/Death Events? -- V5.6
strSHAR = " " -- i.e. Cater for any combination of Witnessed Facts and DNA Birth/Death Events
end
local is_SHAR = false
local isBIRT = false
local isDEAT = false
local arrLine = {}
local arrNumb = {}
local dicIndi = {} -- List of Witness Individual Records -- V5.6
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 = {}
dicIndi = {} -- V5.6
dicIndi["@"..strRid.."@"] = true
if strTag == "BIRT" then isBIRT = true end
if strTag == "DEAT" then isDEAT = true end
elseif strTag == strSHAR then -- Shared/Witnessed Fact found -- V5.6
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 -- V6.4
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 Unknown" -- 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 Unknown") -- V3.3
arrLine[2] = strType
else -- Else add TYPE for standard Event/Attribute
local strType = dicTag.Name:gsub("^.- (.+):%s+$","2 TYPE "..strPrefix.."%1 Role Unknown") -- V3.3
table.insert(arrLine,2,strType)
table.insert(arrNumb,2,arrNumb[1])
end
table.insert(arrLine,"2 _SHAR @"..strRid.."@") -- Synthesise unique ~Principal~ Role -- V5.6
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
if not dicIndi[strVal] then
local strRule, strType = getElements(strVal:gsub("@",""))
addLinkRecRule(strRule,strType,doShareFact,#arrFact) -- Create record tag Rule for new Witness Individual Record -- V5.6
dicIndi[strVal] = true
end
end
if not (strLine:match("^2 HUSB") or strLine:match("^2 WIFE") or strLine:match("^%d AGE")) then -- V3.3 -- V5.6
table.insert(arrLine,strLine) -- Save the Fact Lines and Line Nos except Family fact Ages
table.insert(arrNumb,arrLineNo[intLine])
end
end
if isMake then -- Are synthetic DNA Birth/Death Events needed? -- V5.0
local strRule, strType = getElements(strRid)
if not isBIRT then -- Yes, make Birth from 1st dated fact with Timeframe=Soon After Birth -- V5.0
addLinkRecRule(strRule,strType,doMakeEvent,"SOON-AFTER-BIRTH")
end
if not isDEAT then -- Yes, make Death from 1st dated fact with Timeframe=Post-Death -- V5.0
addLinkRecRule(strRule,strType,doMakeEvent,"POST-DEATH")
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 dicRule[" CENS"] then
doConJoin(tblRecord,arrLineNo)
for intLine = 2, #tblRecord do -- If Census 'Copy TfS to Note' rule exists then search Source record -- V5.3
local strLine = tblRecord[intLine]
if strLine:match("^1 TEXT") then
if not dicSour[strRid] then dicSour[strRid] = {} end
dicSour[strRid].TfS = strLine -- Save the Text from Source tag line -- V5.3
break
end
end
end
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 -- V6.0
if 1 == iup_gui.WarnDialogue(" Tidy Place Record Name Conflicts ","\n See the 'Extra Options' tab, 'Tidy Places && Addresses' option. \n\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 the Place Record Name Conflicts \n so they can be merged or edited but no GEDCOM file will be exported. \n\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 doUniqueId(strUID) -- Handle Fact Definitions after Header or format Unique Id _UID elsewhere (was doFactDefs) -- V5.0
if isRecord("INDI") then -- Format Unique Identity -- V5.0
local strHex = strUID:gsub("-",""):upper() -- Determine whether passed UID is valid (keep same value if not)
if tonumber(strHex,16) and (#strHex == 32 or #strHex == 36) then-- Hexadecimal and valid length
local strHex32 = strHex:sub(1,32)
local intAlpha = 0
local intBravo = 0
for strByte in strHex32:gmatch("%x%x") do -- Calculate checksum using published method
local intValue = tonumber(strByte,16)
intAlpha = intAlpha + intValue
intBravo = intBravo + intAlpha
end
local strCheck = string.format('%x',intAlpha):sub(-2) .. string.format('%x',intBravo):sub(-2)
local strHex36 = strHex32 .. strCheck:upper()
if #strHex == 32 or strHex36 == strHex then -- Valid original checksum
if StrName == "MFT" then -- My Family Tree prefers hypenated GEDCOM 7.0 format with UID tag
return "1 UID "..strHex:sub(1,8)..'-'..strHex:sub(9,12)..'-'..strHex:sub(13,16)..'-'..strHex:sub(17,20)..'-'..strHex:sub(21,32)
elseif StrName == "MYH" then -- My Heritage prefers unhyphenated without checksum
return "1 _UID "..strHex32
else -- Legacy, RootsMagic, et al, prefer unhyphenated with checksum
return "1 _UID "..strHex36
end
end
end
return nil -- Retain original line with invalid UID
end
-- Add Fact Definitions after Header or find Facts to make Birth/Death events for DNA -- V5.0
if not(isRecord("HEAD") and (isFact or isMake)) then return "" end -- If not HEAD record or disabled then just remove original line -- V3.3 -- V5.0
local tblFact = {} -- Fact definitions table
local dicLine = { Name="1 _NAME "; Label="1 _LABEL "; Abbr="1 _ABBR "; Template="1 _SENT "; Role="1 _ROLE "; Verb="2 _VERB "; Sentence="2 _SENT "; }
arrMake = {} -- Facts with Timeframe=SOON-AFTER-BIRTH or Timeframe=POST-DEATH -- V5.0
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
local strName = "" -- Name of tabulated Timeframe facts -- V5.0
local strFactsAnsi, wasAnsi = general.FileNameToANSI(strFactsFile,"Export Gedcom File Facts.fhf") -- V5.0
if not wasAnsi then doCopyFile(strFactsFile,strFactsAnsi) end -- V5.0 -- V5.2
for strLine in encoder.FileLines(strFactsAnsi,"UTF-8") do -- Read each line from Fact Set file -- V5.0 -- 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("^([_ATREVN]+)%-.-%-([IF][AE])$","%1-%2") -- 28 Nov 2016 for 5.5 to match Root array below < this is current
strName = strFact:gsub("%-[IF][EA]$","") -- Name of fact for isMake Timeframe -- V5.0
end
else
if isFact then -- Tabulate fact definition tag lines -- V5.0
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
if isMake then -- Tabulate any Facts with required Timeframe -- V5.0
if strLine:match("^Name=") then
if strName:match("^EVEN")
or strName:match("^_ATTR") then -- Adjust name of custom facts to match GEDCOM -- V5.0
strName = strName:match("^([_%u]+%-).+")..strLine:match("^Name=(.+)")
end
elseif strLine == "Timeframe=SOON-AFTER-BIRTH" or strLine == "Timeframe=POST-DEATH" then
arrMake[strName] = strLine:match("^T%l+=([%u%-]+)") -- Tabulate each fact name with its Timeframe -- V5.0
end
end
end
end
if not wasAnsi then general.DeleteFile(strFactsAnsi) end -- V5.0
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
local strGroupAnsi, wasAnsi = general.FileNameToANSI(strGroupIndex,"Export Gedcom File GroupIndex.fhdata") -- V5.0
if not wasAnsi then doCopyFile(strGroupIndex,strGroupAnsi) end -- V5.0 -- V5.2
for strLine in encoder.FileLines(strGroupAnsi,"UTF-8") do -- Read each line in Project only GroupIndex -- V5.0 -- V4.2 -- V5.9
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
if not wasAnsi then general.DeleteFile(strGroupAnsi) end -- V5.0
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,"UTF-8") do -- Read each line in ProgramData GroupIndex -- V3.6 -- V5.9
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
local strLanguage = iup_gui.GetRegKey("HKCU\\SOFTWARE\\Calico Pie\\Family Historian\\2.0\\Preferences\\Output Language Id")
if strLanguage and #strLanguage ~= 0 then -- Import Language Fact Set file last -- V5.4
doLoadFactsTable(fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Lang\\"..strLanguage.."\\lang_facts.fhf")
end
if isFact then
for intFact, arrFact in ipairs (tblFact) do
for intFact, strLine in ipairs (arrFact) do -- Export Fact Tags from Fact table
doInsert(strLine)
end
end
end
return "" -- Delete original line
end -- local function doUniqueId
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 doBapm2Chr() -- Convert BAPtisM to CHRistening when no BIRTh for TNG -- V5.1
for intLine = 2, tblRecord.Line-1 do
if tblRecord[intLine]:match("^1 BIRT") then return nil end -- Retain original line
end
return "1 CHR"
end -- local function doBapm2Chr
local function doTfS2Note() -- Copy 1st source citation Text from Source to CENSus local Note -- V5.3
if isRecord("INDI") then
local strText = nil
local intNote = nil
local strNote = ""
local intLine = tblRecord.Line
for intLine = intLine+1, #tblRecord do
local intLev, strTag, strVal = getParts(tblRecord[intLine])
if intLev == 2 then
if strTag == "NOTE" then -- Find existing Note
intNote = intLine
strNote = strVal.."\r"
elseif strTag == "SOUR" and not strText then -- Find 1st Source Text from Source
local strRid = strVal:match("^@(S%d+)@")
if strRid and dicSour[strRid] then
strText = dicSour[strRid].TfS
end
end
elseif intLev == 1 then -- Find end of Census event
if strText then
local _, _, strVal = getParts(doPrune(strText)) -- Copy Text from Source to Note
if intNote then
doModify(strNote..strVal,intNote)
else
doInsert("2 NOTE "..strVal,intLine)
end
xxx=0
end
break
end
end
end
return nil -- Retain original line
end -- local function doTfS2Note
local function doCens2Resi() -- Convert Individual CENSus to RESIdence for Ancestry Census hints -- V3.4
local dicRule = dicRule[" CENS"] or {}
local strRule = dicRule.Item or " "
if strRule:match("^Copy T f S to Note") then doTfS2Note() end -- Are both CENSus rules enabled? -- V5.3
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("%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 GKP GSP HER MFT MYH 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 -- V6.0 GKP MYH
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" ;"Handle Unique Id" ; "p" ;"^1 _UID (.*)" ;doUniqueId ;nil ;"Std fh5 fh6" }) -- V3.3 All can export Fact Definitions if option enabled, except fh5 -- V4.0 and fh6 -- V5.0
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 Max" ; "A" ;"^2 _SHA%u (.*)" ;doWitness ;"Witness Role" ;"Std+" }) -- V1.6 for FH V6.0 Rule index "2 _SHAR" and "2 _SHAN"
addRule({"Witness Role" ;"Move to Fact Note Min" ; "F" ;"^2 _SHA%u (.*)" ;doWitness ;"Witness Role" ;"TNG-" }) -- V5.6 for TNG with only Principal in synthetic Fact Witness Fact Note
addRule({"Witness Role" ;"Remove entirely" ; "C" ;"^(2) _SHA%u .*" ;doRemoveAll ;nil ;"Std- FTL tng" }) -- 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" }) -- 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({"Place Record" ;"Make Location Record" ; "H" ;"^0 (@P%d+@ )(.*)" ;doLocation ;nil ;"GKP" }) -- V6.0 GKP
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({"Date Field" ;"BET/AND to Q1-4 yyyy" ; "H" ;"^%d DATE .*" ;doDateField ;"Quarters" ;" " }) -- V5.1 BET/AND => Q1-4 yyyy
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 GKP 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 GKP 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 (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 (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({"Standard Facts" ;"Blank Date,Place,Note" ; "p" ;"^1 ([A-Z]+) ?(.*)";doBlankFact ;nil ;"ANC " }) -- V5.9 ANC Fact with no Date, Place, Note, etc needs a local Note
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 GKP GSP GWC HER LFT MFT MYH 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 -- V5.1 ANC -- TNG see http://www.tng.lythgoes.net/wiki/index.php?title=Desktop_gotchas#Family_Historian_.28FH.29 -- V6.0 GKP MYH=EVEN
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 MYH RMT TNG ZPG " }) -- V4.0 -- V4.4 GWC -- V5.1 ANC -- V6.0 MYH=EVEN
addRule({"Custom Attribute" ;"Keep Standard Tag" ; "C" ;"^1 FACT" ;"1 FACT" ;nil ;"Str AGS FTM GKP GSP GST HER MFT RFT CEB " }) -- V4.0 GEDCOM 5.5.1 -- V6.0 GKP
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" }) -- V2.6 -- V3.1 gst & fh5 -- V4.0 fh6 -- V5.6 gst removed
addRule({"Same Sex Wife" ;"Asymmetric Format" ; "a" ;"^1 (WIFE) (.*)" ;doSameGender;"1 HUSB " ;"Std fh5 fh6" }) -- V2.6 -- V3.1 gst & fh5 -- V4.0 fh6 -- V5.6 gst removed
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({"Baptism Event" ;"Change to Christening" ; "a" ;"^1 BAPM" ;doBapm2Chr ;nil ;"TNG " }) -- V5.1 TNG
addRule({"Census Event" ;"Copy T f S to Note" ; "a" ;"^%d CENS" ;doTfS2Note ;nil ;" " }) -- V5.3 Option for TNG, etc
addRule({"Census Event" ;"Change to Residence" ; "a" ;"^1 CENS" ;doCens2Resi ;nil ;"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 GKP LFT MFT RFT TNG CEB" }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT LFT MFT TNG -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP LFT MFT RFT TNG CEB" }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT LFT MFT TNG -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP 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? -- V6.0 GKP
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 GKP 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? -- V6.0 GKP
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 GKP LFT TNG" }) -- V4.0 FH5 FH6 LFT TNG -- V6.0 GKP
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 GKP LFT TNG" }) -- V4.0 FH5 FH6 LFT TNG -- V6.0 GKP
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 ;"Family Note" ;" " }) -- V3.3
addRule({"Record Note" ;"Move to a Note Fact" ; "F" ;"^1 NOTE (.*)" ;doNote2Fact ;"Record Note" ;"ANC-" }) -- V5.9 ANC- Custom Fact Note
addRule({"Record Note" ;"Citation Note" ; "D" ;"^(1) NOTE (.*)" ;doCiteNote ;"Record Note" ;"ANC+" }) -- V3.3 -- V2.2 ANC -- V5.9 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 -- V5.9 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
addRule({"Subsidiary Note" ;"Weblink to WebTag URL" ; "G" ;"^(%d) NOTE (.*)" ;doWebTags ;nil ;"RMT+" }) -- V5.3
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"; };
CROP = { Lab="Caption Note"; Mode="CROP"; Pref=" _PRIM Y"; };
}
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 ;"GKP" }) -- V1.3 Rule index " OBJE" -- V2.5 L="Caption Note"; -- V6.0 GKP
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
addRule({"Media Object" ;"Media Conversion" ; "w" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.CROP ;"TNG" }) -- V1.3 Rule index " OBJE" -- V2.5 L="Caption Note"; -- V2.1 & V6.2 TNG
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 GKP GSP HER MFT MYH 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 -- V6.0 GKP MYH
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 GKP GSP HER MFT MYH 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 -- V6.0 GKP MYH
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({"Templated Source" ;"Convert to Notes" ; "A" ;"^1 _SRCT (.*)" ;doSrcTempLnk;"Metafield" ;"Std+" }) -- V4.0 Change all Source Template links to Note record links & _FIELD to Metafield labelled Notes & add Footnote, Short Footnote & Bibliography labelled Notes -- V5.5
addRule({"Templated Source" ;"Remove entirely" ; "B" ;"^1 _SRCT (.*)" ;doSrcTempLnk;nil ;"Std-" }) -- V4.0 Remove all Source Template links
addRule({"Templated Source" ;"Keep Templated Source" ; "C" ;"^1 _SRCT" ;"1 _SRCT" ;nil ;"GST+" }) -- V4.0 Retain all Source Template links
addRule({"Templated Source" ;"Footnote to Title" ; "D" ;"^1 _SRCT (.*)" ;doSrcTempLnk;"Reformat" ;"RMT+ TNG+" }) -- V4.2 As "Move into Notes" & put Footnote in Title & Title in Short Title -- V5.3 default RMT+ -- V5.5 default TNG+
addRule({"Templated Source" ;"Handle Citation Field" ; "p" ;"^(%d) _FIELD .*" ;doSrcTempCit;"Metafield" ;"Std " }) -- V4.2 Convert Templated Source Citations according to above choice
addRule({"Templated Source" ;"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" ;"Move to Task Record" ; "D" ;"^(%d) _RNOT (.*)" ;doResNoteLnk;"Task Record" ;" " }) -- V6.0 Change all Research Note links to Note record links with Task Records linked to Note -- GKP
addRule({"Research Note" ;"Move to Researches" ; "E" ;"^(%d) _RNOT (.*)" ;doResNoteLnk;"Researches" ;"GKP+" }) -- V6.0 Change all Research Note links to Note record links with Research Records linked to Note -- GKP
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({"Text From Source" ;"Weblink to WebTag URL" ; "q" ;"^(%d) TEXT (.*)" ;doWebTags ;nil ;"RMT+" }) -- V5.3 RMT
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" ; "a" ;"^(%d) _QUAY (.*)" ;doAssess ;nil ;"Std gst" }) -- V4.0 Convert FH V7 _QUAY Assessment to GEDCOM standard QUAY -- V5.6 Add option to inhibit
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 -- V5.0 -- Use IntMode, StrAbbr, StrMode, StrName, StrNode, IntNode, StrFull below
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(StrName:lower()) ) -- Include rule for "Std" unless excluded by lower-case mode such as "fh5 ftl gft" -- V5.0 StrName instead of StrAbbr
or strRule:matches(StrNode) -- V5.0 -- StrNode instead of StrMode
or strRule:match(" "..StrName.." ") then -- Include rule if its GEDCOM mode matches current GEDCOM export mode -- V5.0 StrName instead of StrAbbr
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 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@"
or dicRule[strKey:gsub("1 [A-Z]+","1 AZ")] -- Special all Facts rule for ANC -- V5.9
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,dicSour -- Return function methods & dicSour -- V5.5
end -- function MakeRules
function GUI_MainDialogue() -- Graphical User Interface
progbar.Setup() -- Popup in middle
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 == "-" or strValue == "OFF" then -- "-" for Full/Brief or "OFF" for Project/Global -- V5.0
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
StrGedcomExportMode() -- GEDCOM destination = Std+, FH5+, FTM-, FMP-, etc, Abbr = Std, FH5, FTM, TNG, etc. -- V3.3
-- Create Basic Options tab 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 Destination:"; } -- V3.3 -- V4.0 -- V5.0 -- was "GEDCOM Export Mode:"
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 lblAdviceL = iup.label { Alignment="ALEFT:ACENTER"; Title="See media image "; } -- V5.0
local lblAdviceC = iup.label { Alignment="ALEFT:ACENTER"; Title="Shared Settings "; } -- V5.0
local lblAdviceR = iup.label { Alignment="ALEFT:ACENTER"; Title="below"; } -- V5.0
local boxAdvice = iup.hbox { lblAdviceL; lblAdviceC; lblAdviceR; Expand="NO"; } -- V5.0
local lblObject = iup.label { Alignment="ARIGHT:ATOP"; Title="Multimedia Preference:"; } -- V3.3 -- V4.0 -- V5.0 -- was "Multimedia Conversion:"
local lstObject = iup.list { DropDown="YES"; Visible_Items="99"; }
local boxObject = iup.hbox { Homogeneous="YES"; iup.hbox { boxAdvice; lblObject; Margin="0x0"; }; lstObject; } -- V4.0 -- V5.0 -- V5.6
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 Destination (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 lblMaxUsed = iup.label { Alignment="ARIGHT:ATOP"; Title="Maximum times the same Rule is listed in Result Set:"; } -- V4.0 -- V5.0
local txtMaxUsed = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinAlign="RIGHT"; SpinInc=1; SpinMin=3; SpinMax=999; }
local boxMaxUsed = iup.hbox { lblMaxUsed; txtMaxUsed; Expand="NO"; Gap="6"; Margin="12x0"; } -- V5.0
local btnChoose = iup.button{ Title="Choose Export Folder"; }
local btnDelete = iup.button{ Title="Empty Export Folder"; }
local boxButtons = iup.hbox { Homogeneous="YES"; btnChoose; btnDelete; Gap="12"; Margin="12x12"; } -- V5.0
local boxSetLeft = iup.vbox { boxMaxUsed; boxButtons; Gap="0"; Margin="0x0"; } -- V5.0
local lblMaxHigh = iup.label { Alignment="ARIGHT:ATOP"; Title="Max image 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 image width:"; } -- V4.0
local txtMaxWide = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinAlign="RIGHT"; SpinInc=1; SpinMin=100; SpinMax=9999; } -- V3.7
local boxMaxSize = iup.hbox { lblMaxHigh; txtMaxHigh; lblMaxWide; txtMaxWide; Expand="NO"; Gap="6"; } -- V5.0
local tglFrames = iup.toggle{ Title=" Use the JPEG file format for all exported image files"; } -- V3.7 -- V5.0
local tglFullOn = iup.toggle{ Title=" Apply the settings above to all image files not just part frames"; } -- V3.7 -- V5.0
local lblNaming = iup.label { Alignment="ALEFT:ACENTER"; Title="Part Frame File Naming Format:"; } -- V5.0
local lstNaming = iup.list { DropDown="YES"; Expand="YES"; TblName[1]; TblName[2]; TblName[3]; }
local boxNaming = iup.hbox { lblNaming; lstNaming; Expand="NO"; Gap="6"; } -- V5.0
local boxSetRight= iup.vbox { boxMaxSize; tglFrames; tglFullOn; boxNaming; Gap="1"; } -- V5.0
local boxSelect = iup.hbox { Homogeneous="YES"; boxSetLeft; boxSetRight; Gap="10"; Margin="0x0"; } -- V3.7
local lblFolder = iup.label { Alignment="ALEFT"; Title=TblOption.Path; }
local tglDirSep = iup.toggle{ Title=" Use / path separators"; } -- V6.0
local tglFolder = iup.toggle{ Title=" Replicate Media folders"; } -- V2.9
local boxFolder = iup.hbox { lblFolder; tglDirSep; tglFolder; Gap="10"; Margin="10x2"; } -- V2.9 -- V5.0 -- V6.0
local boxShared = iup.vbox { boxSelect; boxFolder; Margin="2x2"; } -- V2.9 -- V5.0
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 { iup.label{Expand="YES"}; btnExport; iup.label{Expand="YES"}; Margin="2x12"; }
-- Create Extra Options tab GUI controls
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"; }
-- Create Other Options tab GUI controls
local tglMakeDNA = iup.toggle{ Title=" Make missing Birth/Death events for DNA products"; } -- V5.0
local btnOthers = iup.button{ Title="Reset these (TBD) Options"; }
-- Create Labels Set A/B tabs GUI controls
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
-- Create Manage Settings tab GUI controls -- V5.0 -- New Manage Settings tab
local lblWhere = iup.label { Alignment="ACENTER"; Title="Choose where the Options and Labels tab settings are held for the current Project:"; }
local tglProj = iup.toggle{ Title="Project local data "; }
local tglGlob = iup.toggle{ Title="Global ProgramData "; }
local radWhere = iupRadio ( TblOption.Proj, tglProj, tglGlob )
local btnPrj2Glb = iup.button{ Title="Copy all the settings FROM this Project local data TO the Global ProgramData"; }
local btnGlb2Prj = iup.button{ Title="Copy all the settings FROM the Global ProgramData TO this Project local data"; }
local lblCopyOpt = iup.label { Alignment="ACENTER"; Title="Copy the Options tab settings for current GEDCOM destination: "; }
local btnCEA = { }
btnCEA.Put = iup.button{ Title="Copy the settings from (XYZ) to (CEA)"; }
btnCEA.Get = iup.button{ Title="Copy the settings from (CEA) to (XYZ)"; }
btnCEA.Set = iup.button{ Title="Reset settings for (CEA) to default Std"; }
local btnCEB = { }
btnCEB.Put = iup.button{ Title="Copy the settings from (XYZ) to (CEB)"; }
btnCEB.Get = iup.button{ Title="Copy the settings from (CEB) to (XYZ)"; }
btnCEB.Set = iup.button{ Title="Reset settings for (CEB) to default Str"; }
local boxPutCE = iup.hbox { btnCEA.Put; btnCEB.Put; Margin="0x0"; }
local boxGetCE = iup.hbox { btnCEA.Get; btnCEB.Get; Margin="0x0"; }
local boxSetCE = iup.hbox { btnCEA.Set; btnCEB.Set; Margin="0x0"; }
-- Create the common buttons GUI controls
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 boxManage = iup.vbox { Gap="10"; Margin="110x10"; Alignment="ACENTER"; lblWhere; radWhere; btnPrj2Glb; btnGlb2Prj; lblCopyOpt; boxPutCE; boxGetCE; boxSetCE; } -- V5.0
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
boxManage; TabTitle5=" Manage Settings "; -- V5.0
}
if IntFhVersion > 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,dicSour = 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() -- Save savings adjusted to rule letter
local intText = 0
if tblMode["%d NOTE"].Item[tblMode["%d NOTE"].Pick]:match(" WebTag ") then
intText = 1 -- Ensure %d TEXT rule follows %d NOTE rule for _WEBTAG URL -- V5.3
end
tblMode["%d TEXT"].Pick = intText
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
local strPath = TblOption.Path
local intPath = #strPath
if intPath > 80 then -- Constrain length of Export folder path -- V5.0
local arrDir = strPath:split("\\")
local intDir = 1
local intSum = 0
repeat
local strDir = arrDir[intDir]
intPath = intPath - #strDir - 1 -- Subtract each subfolder length from path length
if intSum <= 20 then
intSum = intSum + #strDir + 1 -- Retain at least 20 characters of leading path
intDir = intDir + 1
else
if intPath > 80 then
table.remove(arrDir,intDir) -- Remove subfolders from path to reduce length below 80 characters
else
arrDir[intDir] = " ... " -- Insert ellipsis in place of removed subfolders
break
end
end
until false
strPath = table.concat(arrDir,"\\") -- Rebuild constrained path
end
lblFolder.Title = strPath -- V5.0
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 setControlsActive(strYorN) -- Enable/Disable controls during busy actions
-- strYorN ~ "YES" or "NO"
tabControl.Active = strYorN
boxButtons.Active = strYorN
doUpdateFolder() -- V5.1
if StrAbbr:match("CE[AB]") then -- Disable label when CEA/B current -- V5.0
lblCopyOpt.Active = "NO"
else
lblCopyOpt.Active = strYorN
end
for strAbbr, dicBtn in pairs ({ CEA=btnCEA; CEB=btnCEB; }) do -- Check the CEA and CEB buttons -- V5.0
if ( TblOption[strAbbr].Mode or StrAbbr ) ~= StrAbbr
or StrAbbr:match("CE[AB]") then -- Disable copy to CEA/B buttons -- V5.0
dicBtn.Put.Active = "NO"
else
dicBtn.Put.Active = strYorN
end
if TblOption[strAbbr].Mode == StrAbbr then -- Enable copy from or reset CEA/B -- V5.0
dicBtn.Get.Active = strYorN
dicBtn.Set.Active = strYorN
else
dicBtn.Get.Active = "NO"
dicBtn.Set.Active = "NO"
end
end
if type(TblOption.Obje) ~= "number" then
--? fhMessageBox("setControlsActive(strYorN) TblOption.Obje = "..(TblOption.Obje or "nil"),"MB_OK","MB_ICONEXCLAMATION") --?
TblOption.Obje = ""
end
local strObje = TblObje[tonumber(TblOption.Obje) or 1].Abbr
if strObje:match("^FILE~") -- Shared Settings for Media files only apply to exported images -- V5.0
or strObje:match("^WIPE~") then
strYorN = "NO" -- Disable the Shared Settings for Media files -- V5.0
end
boxSetRight.Active = strYorN -- Max height/width, JPEG toggle, Apply to all, Frame Naming -- V5.0
tglFolder .Active = strYorN -- Keep Media folders -- V5.0
end -- local function setControlsActive
local function setFullMode() -- Set Full v Brief radio toggles -- V3.3 -- V5.0
tglFull.Tip = tglFull.Tip
tglTrim.Tip = tglTrim.Tip -- Refresh XP Tooltips
TblOption[StrAbbr] = TblOption[StrAbbr] or {} -- V5.0
if tglFull.Value == "ON" then
TblOption[StrAbbr].Full = "+"
else
TblOption[StrAbbr].Full = "-"
end
StrGedcomExportMode() -- V5.0 -- Use IntMode, StrMode, StrAbbr, StrFull
if not TblOption[StrMode] then
ResetGedcomOptions(TblMode[IntMode],strFull)
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
StrGedcomExportMode() -- V3.3 -- V5.0 -- Use StrMode, StrAbbr
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() -- V3.3 -- V5.0
btnBasics.Title = "Reset these Destination ("..StrMode..") Options" -- Update reset button Title for current GEDCOM Destination -- V4.0 -- V5.0
local intNode = TblMode[TblOption["CEA"].Mode or "CEA"]
lstGedcom[TblMode["CEA"]] = " (CEA) "..TblMode[intNode].Full -- Update the GEDCOM destination CEA droplist entry
local intNode = TblMode[TblOption["CEB"].Mode or "CEB"]
lstGedcom[TblMode["CEB"]] = " (CEB) "..TblMode[intNode].Full -- Update the GEDCOM destination CEB droplist entry
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
tglDirSep.Value = TblOption.Seps -- V6.0
tglFolder.Value = TblOption.Keep -- V2.9
txtMaxHigh.SpinValue = TblOption.High -- Including spin values
txtMaxWide.SpinValue = TblOption.Wide
txtMaxUsed.SpinValue = TblOption.Used
setControlsActive("YES") -- Shared Settings for Media files -- V5.0
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
tglMakeDNA.Value = TblOption[StrMode].Make -- V5.0
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
radWhere.Value = iupValue(TblOption.Proj, tglProj, tglGlob) -- V5.0 From here on
lblCopyOpt.Title = "Copy the Options tab settings for current GEDCOM destination: "..lstGedcom[lstGedcom.Value]
for strAbbr, dicBtn in pairs ({ CEA=btnCEA; CEB=btnCEB; }) do -- Set the CEA and CEB button titles -- V5.0
local strTarg = TblOption[strAbbr].Mode or "n/a"
local strSour = TblOption[strAbbr].Mode or StrAbbr
if strSour == strAbbr then strSour = "n/a" end
dicBtn.Put.Title = "Copy the settings from ("..strSour..") to ("..strAbbr..")"
dicBtn.Get.Title = "Copy the settings from ("..strAbbr..") to ("..strTarg..")"
end
doSaveSettings() -- Save sticky data settings
end -- local function setControls
local tipAdvice = "See the Shared Settings options for Media images below"
local tipResets = "Reset these options to defaults for the GEDCOM destination product"
-- 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, and label options, or manage settings"; };
[tglFull] = { "FontHead"; "Safe"; "0x0"; "Comprehensive Project GEDCOM details"; }; -- V3.3
[tglTrim] = { "FontHead"; "Safe"; "0x0"; "Abbreviated Project GEDCOM details"; }; -- V3.3
[lblAdviceL]= { "FontBody"; "Body"; "0x0"; tipAdvice; };
[lblAdviceC]= { "FontHead"; "Body"; "0x0"; tipAdvice; };
[lblAdviceR]= { "FontBody"; "Body"; "0x0"; tipAdvice; };
[lblGedcom] = { "FontHead"; "Body"; "4x2"; "Choose the GEDCOM export rules for destination product"; };
[lstGedcom] = { "FontBody"; "Safe"; "0x0"; "Choose the GEDCOM export rules for destination product"; };
[lblObject] = { "FontHead"; "Body"; "4x2"; "Choose the Multimedia preference for destination product"; };
[lstObject] = { "FontBody"; "Safe"; "0x0"; "Choose the Multimedia preference for destination product"; };
[lblEncode] = { "FontHead"; "Body"; "4x2"; "Choose GEDCOM character encoding for destination product"; };
[lstEncode] = { "FontBody"; "Safe"; "0x0"; "Choose GEDCOM character encoding for destination product"; };
[lblPrivate]= { "FontHead"; "Body"; "4x2"; "Choose whether to exclude [[private]] text for destination product"; }; -- V3.2
[lstPrivate]= { "FontBody"; "Safe"; "0x0"; "Choose whether to exclude [[private]] text for destination product"; }; -- V3.2
[lblRichtxt]= { "FontHead"; "Body"; "4x2"; "Choose the rich text formatting for destination product"; }; -- V4.0
[lstRichtxt]= { "FontBody"; "Safe"; "0x0"; "Choose the rich text formatting for destination product"; }; -- V4.0
[btnBasics] = { "FontBody"; "Safe"; "0x0"; tipResets; }; -- V4.0
[frmShared] = { "FontHead"; "Body"; false; };
[lblMaxUsed]= { "FontBody"; "Body"; "0x0"; "Set display limit for same Rule listed in Result Set"; };
[txtMaxUsed]= { "FontBody"; "Safe"; "0x0"; "Set display limit for same Rule listed in Result Set"; };
[lblMaxHigh]= { "FontBody"; "Body"; "0x0"; "Set Multimedia file maximum pixel height for all destinations"; };
[txtMaxHigh]= { "FontBody"; "Safe"; "0x0"; "Set Multimedia file maximum pixel height for all destinations"; };
[lblMaxWide]= { "FontBody"; "Body"; "0x0"; "Set Multimedia file maximum pixel width for all destinations"; };
[txtMaxWide]= { "FontBody"; "Safe"; "0x0"; "Set Multimedia file maximum pixel width for all destinations"; };
[tglFrames] = { "FontBody"; "Safe"; "0x0"; "Use JPEG format where possible for all image files for all destinations"; }; -- V1.4 -- V3.7
[tglFullOn] = { "FontBody"; "Safe"; "0x0"; "Choose to include all image files in these settings for all destinations,\n otherwise only part-frame files are included"; }; -- V3.7
[lblNaming] = { "FontBody"; "Body"; "0x0"; "Choose the part-frame file naming format for all destinations"; };
[lstNaming] = { "FontBody"; "Safe"; "0x0"; "Choose the part-frame file naming format for all destinations"; };
[btnChoose] = { "FontBody"; "Safe"; "0x8"; "Choose GEDCOM and Media files export folder path for all destinations"; }; -- V4.0 was "1x1"
[btnDelete] = { "FontBody"; "Risk"; "0x8"; "Empty GEDCOM and Media files from export folder for all destinations"; }; -- V4.0 was "1x1"
[lblFolder] = { "FontHead"; "Body"; "0x0"; "Current GEDCOM and Media files export folder path for all destinations"; };
[tglDirSep] = { "FontBody"; "Safe"; "0x0"; "Choose to use MacOS/Unix style / path separators in export folder"; }; -- V6.0
[tglFolder] = { "FontBody"; "Safe"; "0x0"; "Choose whether to replicate Media subfolders in the export folder"; }; -- 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
[tglMakeDNA]= { "FontBody"; "Safe"; "0x0"; "Synthesise Birth/Death events for DNA from Baptism, Christening, Burial, Cremation, etc."; }; -- V5.0
[btnExtras] = { "FontBody"; "Safe";"34x2"; tipResets; };
[btnOthers] = { "FontBody"; "Safe";"34x2"; tipResets; };
[btnLabelA] = { "FontBody"; "Safe"; "0x0"; "Reset these options to defaults for all GEDCOM destination products"; }; -- V3.3
[btnLabelB] = { "FontBody"; "Safe"; "0x0"; "Reset these options to defaults for all GEDCOM destination products"; }; -- V3.3
[lblWhere] = { "FontHead"; "Body"; "0x0"; "Where the current Options and Labels tab settings are held for this project"; }; -- V5.0 hereon
[tglProj] = { "FontHead"; "Safe"; "0x0"; "Employ the Project local data"; };
[tglGlob] = { "FontHead"; "Safe"; "0x0"; "Employ the Global ProgramData"; };
[btnPrj2Glb]= { "FontBody"; "Safe"; "0x0"; "Copy all the settings in this Project to the Global ProgramData"; };
[btnGlb2Prj]= { "FontBody"; "Safe"; "0x0"; "Copy all the settings in the Global ProgramData to this Project"; };
[lblCopyOpt]= { "FontHead"; "Body"; "0x12";"Copy the Options tab settings of destination product to/from CEA/B"; };
[btnCEA.Put]= { "FontBody"; "Safe"; "0x0"; "Copy from product settings to (CEA) Custom Export Alpha"; };
[btnCEA.Get]= { "FontBody"; "Safe"; "0x0"; "Copy from (CEA) Custom Export Alpha to product settings"; };
[btnCEA.Set]= { "FontBody"; "Safe"; "0x0"; "Reset (CEA) Custom Export Alpha to default Std settings"; };
[btnCEB.Put]= { "FontBody"; "Safe"; "0x0"; "Copy from product settings to (CEB) Custom Export Bravo"; };
[btnCEB.Get]= { "FontBody"; "Safe"; "0x0"; "Copy from (CEB) Custom Export Bravo to product settings"; };
[btnCEB.Set]= { "FontBody"; "Safe"; "0x0"; "Reset (CEB) Custom Export Bravo to default Str settings"; };
[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 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 getTreeID(strAbbr) -- Report Tree ID for products supporting hyperlinks -- V5.6
local strTreeID = ""
if strAbbr == "TNG" or strAbbr == "AnyOther" then
local ptrHead = fhNewItemPtr()
ptrHead:MoveToRecordById("HEAD",0)
strTree = fhGetItemText(ptrHead,"~.DEST") or "" -- Get the Tree ID for hyperlinks from Header Destination -- V5.6
strTree = strTree:match(strAbbr.." -[:=] -(%w+)")
strTreeID = strTree
if not strTreeID then strTreeID = "missing" end
strTreeID = " Tree ID is "..strTreeID
end
return strTreeID
end -- local function getTreeID
local function doPopulateDropLists() -- Populate all the GUI droplists with item choices
local dicFull = {}
for intMode, tblMode in ipairs ( TblMode ) do -- Loop through GEDCOM destinations and cater for CEA/B current product -- V5.0
local strFull = tblMode.Full
local strAbbr = tblMode.Abbr
local strMode = TblOption[strAbbr].Mode -- V5.0
if strMode then
strFull = TblMode[TblMode[strMode]].Full -- Lookup its full name
end
strFull = strFull..getTreeID(strAbbr) -- Append its Tree ID -- V5.6
lstGedcom[intMode] = " ("..strAbbr..") "..strFull -- 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{Alignment="ABOTTOM";Expand="NO";Gap="12";btnExtras;tglTidying;tglFactDef;tglPrefix;tglCaption;}) -- 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
if boxLeft then
iup.Append(boxOther,iupHbox(boxLeft,iup.hbox{})) -- Create remaining odd left option box with blank on right -- V5.0
end
iup.Append(boxOther,iup.hbox{Alignment="ABOTTOM";Expand="NO";Gap="280";btnOthers;tglMakeDNA;}) -- Add reset button and Make Birth/Death DNA toggle at bottom -- V5.0
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 -- Add reset button before last/dummy box on Label Set A tab -- V4.0
boxRight = boxLeft
boxLeft = iupHbox(btnLabelA,iup.hbox{})
iup.Append(boxLabel,iupHbox(boxLeft,boxRight or iup.hbox{}))
boxLeft = nil
boxLabel = boxLab_B -- Select Labels Set B tab -- V4.0
end
end
boxRight = boxLeft
boxLeft = iupHbox(btnLabelB,iup.hbox{})
iup.Append(boxLabel,iupHbox(boxLeft,boxRight or iup.hbox{})) -- Add reset button before 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
local function doResetRules() -- Reset the Rules and Controls common action -- V5.0 applies widely
tblMode = setRules() -- Set default dictionary of Rules for current GEDCOM mode
getSpinValues()
setControls() -- Set the Controls and redisplay dialogue
iup_gui.ShowDialogue("Main")
end -- local function doResetRules
function tglFull:action(intState) -- Action for Full v Brief radio toggle -- V3.3
setControlsActive("NO")
setFullMode() -- V5.0
doResetRules() -- V5.0 -- Set default dictionary of Rules for chosen Full v Trim mode
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
StrGedcomExportMode() -- V3.3 -- V5.0 -- Use IntMode, StrMode, StrAbbr, StrName, StrNode, IntNode, StrFull
doResetRules() -- V5.0
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"..fhConvertANSItoUTF8(strPath).."\n")
else
TblOption.Path = strPath -- Update chosen export folder path
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 -- V6.0
local arrFolder = {} -- V5.0
local strExport = TblOption.Path.."\\Media\\"
for _, dicFile in ipairs(general.GetFolderContents(strFolder,true)) do
local strFile = dicFile.path
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 dicFile.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,iup_gui.MemoDialogue) -- Delete only export Media & Gedcom files -- V5.0
end
elseif dicFile.mode == "directory" then
table.insert(arrFolder,strFile) -- Remember all export Media subfolders -- V5.0
end
end
end
for _, strFolder in ipairs(arrFolder) do -- V5.0
general.DeleteFolder(strFolder,iup_gui.MemoDialogue) -- Remove all export Media subfolders -- V5.0
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 tglDirSep:action(intState) -- Action for folder separator toggle -- V6.0
setControlsActive("NO")
TblOption.Seps = tglDirSep.Value
doSaveSettings()
setToolTip(tglDirSep)
setControlsActive("YES")
end -- function tglDirSep: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
function btnExport:action() -- Action for Export GEDCOM & Media button
setControlsActive("NO")
local isOK = false
if StrName == "FMP" -- StrName instead of TblMode[TblOption.Mode].Abbr i.e. StrAbbr -- V5.0
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
StrReformat = "" -- V5.3
getSpinValues()
doSaveSettings()
setRules(tblMode) -- Reset the Rules according to existing tblMode
isOK = ExportGedcom(doAnalyse,useRules,dicSour) -- Export GEDCOM file using the Analyse & Rules functions
if isOK then return iup.CLOSE end
setControlsActive("YES")
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
function tglMakeDNA:action(intState) -- Action for make Birth/Death events DNA toggle -- V5.0
setControlsActive("NO")
TblOption[StrMode].Make = tglMakeDNA.Value
doSaveSettings()
setToolTip(tglMakeDNA)
setControlsActive("YES")
end -- function tglMakeDNA:action
local function doResetTab(tblTab) -- Reset the Extra/Other Options tab Rules
for intTag, strTag in ipairs ( tblTab ) do
TblOption[StrMode][strTag] = nil -- V5.0 -- StrMode
end
doResetRules() -- V5.0 -- Set default dictionary of Rules for current GEDCOM mode
end -- local function doResetTab
function btnBasics:action() -- Action for Reset Options button on Basic Options tab -- V4.0
setControlsActive("NO") -- V5.0 -- Use StrAbbr, IntMode
ResetGedcomOptions(TblMode[IntMode],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
if strItem ~= "Make" then
TblOption[StrMode][strItem] = TblMode[IntNode][strItem] -- V5.0 -- Use StrMode, IntNode
end
end
doResetTab(tblMode.Drop)
setControlsActive("YES")
end -- function btnExtras:action
function btnOthers:action() -- Action for Reset Options button on Other Options tab
setControlsActive("NO")
TblOption[StrMode].Make = TblMode[IntNode].Make -- V5.0 -- Use StrMode, IntNode
doResetTab(tblMode.Togg)
setControlsActive("YES")
end -- function btnOthers:action
local function doResetLabels() -- Reset all synthetic Labels options -- V4.0
setControlsActive("NO")
ResetSyntheticLabels()
doResetRules() -- V5.0 -- Set default dictionary of Rules for default GEDCOM mode
setControlsActive("YES")
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 tglProj:action(intState) -- Action for Project v Global radio toggle -- V5.0
setControlsActive("NO")
tglGlob.Tip = tglGlob.Tip
tglProj.Tip = tglProj.Tip -- Refresh XP Tooltips
local strProject = TblOption.Proj
TblOption.Proj = tglProj.Value
getSpinValues()
SaveSettings(strProject) -- Save settings in old location with new TblOption.Proj value
LoadSettings(TblOption.Proj) -- Load settings in new location
setControls()
doResetRules()
setControlsActive("YES")
end -- function tglProj:action
function btnPrj2Glb:action() -- Action for Copy settings from Project to Global button -- V5.0
setControlsActive("NO")
if TblOption.Proj == "ON" then
SaveSettings( "OFF" ) -- Save current Project TblOption to Global file
else
LoadSettings( "ON" ) -- Load Project file to current Global TblOption
TblOption.Proj = "OFF"
setControls()
doResetRules() -- Set default dictionary of Rules for current GEDCOM mode
end
setControlsActive("YES")
end -- function btnPrj2Glb:action
function btnGlb2Prj:action() -- Action for Copy settings from Global to Project button -- V5.0
setControlsActive("NO")
if TblOption.Proj == "OFF" then
SaveSettings( "ON" ) -- Save current Global TblOption to Project file
else
LoadSettings( "OFF" ) -- Load Global file to current Project TblOption
TblOption.Proj = "ON"
setControls()
doResetRules() -- Set default dictionary of Rules for current GEDCOM mode
end
setControlsActive("YES")
end -- function btnGlb2Prj:action
local function doCopySettings(strSource,strTarget) -- Copy settings from source to target product -- V5.0
setControlsActive("NO")
local tblOption = TblOption
for _, strTail in ipairs ( { ""; "+"; "-"; } ) do
for j, k in pairs (TblOption[strSource..strTail]) do -- Copy settings from source to target
TblOption[strTarget..strTail][j] = k
end
end
if strTarget == StrAbbr then
doResetRules() -- If Target is current GEDCOM mode, set default dictionary of Rules
else
TblOption[strTarget].Mode = strSource -- If Target is CEA/B then simply update its mode
setControls()
end
setControlsActive("YES")
end -- local function doCopySettings
function btnCEA.Put:action() -- Action for Copy the settings to (CEA) button -- V5.0
doCopySettings(StrAbbr,"CEA")
end -- function btnCEA.Put:action
function btnCEA.Get:action() -- Action for Copy the settings from (CEA) button -- V5.0
doCopySettings("CEA",StrAbbr)
end -- function btnCEA.Get:action
function btnCEA.Set:action() -- Action for Reset settings for (CEA) button -- V5.0
setControlsActive("NO")
TblOption["CEA"] = {}
TblOption["CEA+"] = {}
TblOption["CEA-"] = {}
ResetGedcomOptions(TblMode[TblMode["CEA"]])
setControls() -- Reset controls & redisplay Main dialogue
setControlsActive("YES")
end -- function btnCEA.Set:action
function btnCEB.Put:action() -- Action for Copy the settings to (CEB) button -- V5.0
doCopySettings(StrAbbr,"CEB")
end -- function btnCEB.Put:action
function btnCEB.Get:action() -- Action for Copy the settings from (CEB) button -- V5.0
doCopySettings("CEB",StrAbbr)
end -- function btnCEB.Get:action
function btnCEB.Set:action() -- Action for Reset settings for (CEB) button -- V5.0
setControlsActive("NO")
TblOption["CEB"] = {}
TblOption["CEB+"] = {}
TblOption["CEB-"] = {}
ResetGedcomOptions(TblMode[TblMode["CEB"]])
setControls() -- Reset controls & redisplay Main dialogue
setControlsActive("YES")
end -- function btnCEB.Set: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
doResetRules() -- V5.0 -- Set default dictionary of Rules for default GEDCOM mode
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"; "-manage-settings-tab"; } -- V5.1
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 Destination -- V5.0 StrMode
end
if intNew == 2 then
btnOthers.Title = "Reset these ("..StrMode..") Options" -- Update reset button Title for current GEDCOM Destination -- V5.0 StrMode
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
function ClosingMessage(strOption,strExpNam,strExport) -- V5.6
local lblOption = iup.label { Font=iup_gui.FontBody; FgColor=iup_gui.Body; Alignment="ARIGHT:ATOP"; Title=strOption.." file has been written to the export folder as:"; }
local lblExpNam = iup.label { Font=iup_gui.FontBody; FgColor=iup_gui.Body; Alignment="ARIGHT:ATOP"; Title=strExpNam; }
local lblReform = iup.label { Font=iup_gui.FontHead; FgColor=iup_gui.Risk; Alignment="ARIGHT:ATOP"; Title=StrReformat; }
local timReform = iup.timer { Time=500; action_cb=function() if lblReform.FgColor == iup_gui.Risk then lblReform.FgColor = iup_gui.Body else lblReform.FgColor = iup_gui.Risk end end; }
local lblChoose = iup.label { Font=iup_gui.FontBody; FgColor=iup_gui.Body; Alignment="ARIGHT:ATOP"; Title=" When diagnosing exported GEDCOM problems it may sometimes be useful to \n view the exported file in Family Historian, Notepad, or Windows/File Explorer. "; }
local lblBeware = iup.label { Font=iup_gui.FontHead; FgColor=iup_gui.Risk; Alignment="ARIGHT:ATOP"; Title=" BEWARE: Using Family Historian may disrupt the GEDCOM file format ! ! ! "; }
local lblSepLine = iup.label { Separator="HORIZONTAL"; }
local btnFamHist = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Risk; MinSize="80"; Padding=iup_gui.Margin; Title="Family\nHistorian"; action=function() timReform.Run = "NO" general.DoExecute(strExport) return iup.CLOSE end; }
local btnNotepad = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Warn; MinSize="80"; Padding=iup_gui.Margin; Title="Windows\nNotepad"; action=function() timReform.Run = "NO" general.DoExecute("notepad.exe",strExport) return iup.CLOSE end; }
local btnExplore = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Warn; MinSize="80"; Padding=iup_gui.Margin; Title="Win/File\nExplorer"; action=function() timReform.Run = "NO" general.DoExecute(TblOption.Path) return iup.CLOSE end; }
local btnClosure = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Safe; MinSize="80"; Padding=iup_gui.Margin; Title=" Close\nMemo"; action=function() timReform.Run = "NO" return iup.CLOSE end; }
local boxButtons = iup.hbox { Homogeneous="YES"; Alignment="ACENTER"; btnFamHist; btnNotepad; btnExplore; btnClosure; }
local dialogMemo = iup.dialog{ Title=iup_gui.Plugin..iup_gui.Version.." Closing Memo"; iup.vbox { Alignment="ACENTER"; Gap="9"; Margin="4x4"; lblOption; lblExpNam; lblReform; lblChoose; lblBeware; lblSepLine; boxButtons; }; }
timReform.Run = "YES"
iup_gui.ShowDialogue("Memo",dialogMemo,btnClosure,"popup")
dialogMemo:destroy()
end -- function ClosingMessage
-- Export Gedcom File --
function ExportGedcom(doAnalyse,useRules,dicSour)
-- doAnalyse = Analyse function to save record names, _ROOT, Media _ASID/_SEQ, Witnessed Facts, etc
-- useRules = Rules function to convert records
SetSharedData() -- V4.0
general.MakeFolder(TblOption.Path,iup_gui.MemoDialogue) -- Ensure that the Export folder exists -- V2.9 -- V5.1
local tblMain = iup_gui.DialogueAttributes("Main") -- Obtain position of Main dialogue
local intMainX = tblMain.CoordX
local intMainY = tblMain.CoordY
local strFull = " "..TblMode[IntNode].Full -- Full product name of the chosen Mode of GEDCOM Export -- V5.0 IntNode
local strMode = " "..TblMode[IntMode].Abbr -- Abbreviated export mode "Std", "Min", "FTA", "TNG", etc -- V5.0 IntMode
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 strExpNam = StrFile..strMode..strCode..".ged" -- GEDCOM export file name & type -- V5.0
local strExport = TblOption.Path.."\\"..strExpNam -- GEDCOM export file full path -- V5.0
general.DeleteFile(strExport) -- Delete existing file so new file gets new Creation Date -- V5.0
local strExpAns,wasExpAns = general.FileNameToANSI(strExport,"Export Gedcom File Export.ged") -- V5.0
local putExport = general.OpenFile(strExpAns,"wb") -- V5.0
local strOption = strMode..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 strImpAns,wasImpAns = general.FileNameToANSI(StrImport,"Export Gedcom File Import.ged") -- V5.0
if not wasImpAns then doCopyFile(StrImport,strImpAns) end -- V5.0 -- V5.2
local intBytes = lfs.attributes(strImpAns,"size") -- V5.0
progbar.Setup( { X = intMainX+50; Y = intMainY+50; } ) -- Popup in same place as Main dialogue
progbar.Start("Analysing GEDCOM",intBytes) -- Progress Bar for analysis phase unconditionally -- V5.0
fhSleep(1000,400)
local isConflicts, arrConflict -- Tidy Place Record Name Conflicts signal, array, and Result Set
local arrPlace1 = {}
local arrRecId1 = {}
local arrPlace2 = {}
local arrRecId2 = {}
for strText,strTail,intLump in encoder.FileLines(strImpAns,"UTF-8") do -- V5.0 -- StrImport > strImpAns
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 Tidy Place Record Name Conflicts 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.."~ Tidy Place Record Name Conflicts")
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()
progbar.Setup( { X = intMainX+50; Y = intMainY+50; } ) -- Popup in same place as Main dialogue
progbar.Start("Exporting GEDCOM",intRecord) -- Progress Bar for GEDCOM export phase unconditionally -- V5.0
arrRecord = { } -- Clear arrays for first record
arrLineNo = { }
intLineNo = 0
for strText in encoder.FileLines(strImpAns,"UTF-8") do -- Slow 2nd Pass ~ Read each text line from FH GEDCOM import file -- V1.5 Unicode -- V5.0 StrImport > strImpAns
if not progbar.Stop() then -- Skip Exporting? -- V5.0 -- Allow loop to terminate otherwise general.DeleteFile(strImpAnsi) fails
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)
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
end
if dicSour.SOUR then fhDeleteItem(dicSour.SOUR) end -- Delete dummy Source record -- V5.5
if dicSour.SRCT then fhDeleteItem(dicSour.SRCT) end -- Delete dummy Source Template -- V5.5
fhUpdateDisplay()
--? 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
if not wasExpAns then general.MoveFile(strExpAns,strExport) end -- V5.0
if not wasImpAns then general.DeleteFile(strImpAns) end -- V5.0
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()
ClosingMessage(strOption,strExpNam,strExport) -- V5.6
if #ArrRule > 0 then -- Output Result Set arrays
if fhGetAppVersion() > 5 then
fhSetStringEncoding("UTF-8") -- V4.2 -- V5.7
end
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 messageSource:Export-Gedcom-File-27.fh_lua
--[[
@Title: Export Gedcom File
@Type: Standard
@Author: Mike Tate
@Contributors:
@Version: 6.4
@Keywords:
@LastUpdated: 28 Jul 2025
@Licence: This plugin is copyright (c) 2025 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.
]]
--[=[
--? Special cases, or diagnostics, etc.
TBD: Review GFT for Media Picture Note settings in several rules; Use fhCallBuiltInFunction("TextPart",strText,1,0,"TIDY"):gsub(" +"," "):gsub(" +,",",") in strTidyText();
TBD: Update (BHG) Behold Genealogy https://www.beholdgenealogy.com/blog/?p=3232; Translate labels such as Principal as per Language; doAnalyse() use FH API rather than GEDCOM file; doPrune() < > to < > substitution;
@V6.4: Fix doPrune() strAny bug when level & tag missing; 1 FACT/_ATTR space bug in doSaveFacts();
@V6.3: Fix doWitness(...) _FMT bug; Cater for media\ Media/ media/;
@V6.2: TNG image CROP (FULL~...) feature; Ensure annotation notes get removed;
@V6.1: Library V3.7; Extremely long text limit according to destination and improved error report;
@V6.0: Library V3.6; Improve the Tidy Place Record Name Conflicts message; doEmptyFolder(...) uses general.GetFolderContents(...); Make doCopyFile(...) global; Support (GKP) GEDKeeper Program; Set excessive text to 10,000 chars for GKP; (MYH) MyHeritage to GEDCOM 5.5.1 except FACT tag; Option for / path separators for MacOS, etc;
@V5.9: Fix problems loading Fact Set files with accents/symbols in file path &/or are empty files; Update ANC for Note Citations and Facts with no Date, Place & Note fields; doMediaError(...) function uses first 15 chars to identify similar mesages;
@V5.8: Fix problem in general.MakeFolder(...) when folder path is invalid;
@V5.7: Fix for FH V5 Registry PDX Font and fhSetStringEncoding(...) missing; Omit Source Template Note record if Source Template label is blank; In doReform(..) omit Footnote/Bibliography if blank Label; Fix doSrcTempCit(..) & doSrcTempRec(...) conversion to Notes; Add GST to allow formatted Source record Title;
@V5.6: Add _QUAL Other Option for doAssess(...); New _SHAR Move to Fact Note Min option for TNG with hyperlinks for Tree ID in HEAD.DEST; Improved Fact Witness Facts; Improved closing Memo; GedSite same-sex Asymmetric Format;
@V5.5: In doSrcTempLnk(): Source Title style codes for TNG as well as RMT, add Footnote, Short Footnote & Bibliography labelled Source Note using dummy records;
@V5.4: Exclude 'Book' & 'Title' style code setup in PresetGlobalData() for FH V5/6; Use HKCU Output Language Id to include lang-facts.fhf in exported Sentence Templates;
@V5.3: Fix doInsert() line bug; doConJoin() with table copes with long text; doDisJoin() avoids break on UTF8 and traps excessively long text; Fix FORMat when TblOption.Jpeg == "ON"; WebTag option for _WEBTAG for Source Citation URL; Style codes in Title in doSrcTempLnk(); Add Weblink & Record label options; New rule Text from Source to Census Note doTfS2Note();
@V5.2: Trap errors in doCopyFile(); Remove redundant commas in doKeyword(); Remove _FMT tag from Fact Witness Notes in doWitness(); Omit synthetic Place citation Note in doCite2Note(); Convert REPO even for Remove entirely in doSrcTempLnk(); Fix citation _FIELD bug in doSrcTempCit(); Fix missing Note bug in doSrcTempRec();
@V5.1: BHG placeholder; _ATTR/FACT: Custom Fact/Event & Value for ANC; BET/AND to Qn date option for TNG; BAPM to CHR option for TNG;
@V5.1: Library V3.3; Fix Help for Manage Settings tab; Added DetectOldModules(); Fix Export folder issue found by BillH Mar'22; Fix empty FMP ZIP folder found by jelv Jul'22; Fix doPrune() for rich text;
@V5.0: Library V3.2 FSO Unicode; Synthetic Birth/Death from Bap/Chr/Bur/Crem, etc; _UID formatting; Fix rich text in synthetic Notes; New GUI option names; Manage Settings tab for Global v Project settings & Copy product settings to/from CEA/B;
@V4.6: CheckVersionInStore() at monthly intervals; Citation NOTE Weblink adds _LINK URL & fix copy Media to Citations for FTM;
@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.
]=]
if fhGetAppVersion() > 5 then
fhSetStringEncoding("UTF-8")
end
--[[
@Title: aa Library Functions Preamble
@Author: Mike Tate
@Version: 3.7
@LastUpdated: 12 Dec 2024
@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+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.5
@LastUpdated: 12 Dec 2024
@Description: A general functions module to supplement LUA functions, where filenames use UTF-8 but for a few exceptions.
@V3.5: Further fix for Crossover/WINE file attributes;
@V3.4: Further fix for Unix/WINE file attributes;
@V3.3: Fix problem in fh.MakeFolder(...) when folder path is invalid; Remove Type, ShortName & ShortPath from attributes(...) as unsupported in WINE;
@V3.2: Added function DetectOldModules(); Updated functions RenameFile(), RenameFolder() & GetFolderContents();
@V3.1: Functions derived from FH V7 fhFileUtils library using File System Objects, plus additional features;
@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("luacom") -- To create File System Object
fh.FSO = luacom.CreateObject("Scripting.FileSystemObject")
-- Report error message --
local function doError(strMessage,errFunction)
-- strMessage ~ error message text
-- errFunction ~ optional error reporting function
if type(errFunction) == "function" then
errFunction(strMessage)
else
error(strMessage)
end
end -- local function doError
-- Convert filename to ANSI alternative and indicate success --
function fh.FileNameToANSI(strFileName,strAnsiName)
-- strFileName ~ full file path
-- strAnsiFile ~ ANSI file name & type
-- return values ~ ANSI file path, true if original path was ANSI compatible
if stringx.encoding() == "ANSI" then return strFileName, true end
local isFlag = fhIsConversionLossFlagSet()
fhSetConversionLossFlag(false)
local strAnsi = fhConvertUTF8toANSI(strFileName)
local wasAnsi = true
if fhIsConversionLossFlagSet() then
strAnsiName = strAnsiName or "ANSI.ANSI"
strAnsi = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugin Data\\"..strAnsiName
wasAnsi = false
end
fhSetConversionLossFlag(isFlag)
return strAnsi, wasAnsi
end -- local function FileNameToANSI
-- Get parent folder --
function fh.GetParentFolder(strFileName)
-- strFileName ~ full file path
-- return value ~ parent folder path
local strParent = fh.FSO:GetParentFolderName(strFileName) --! Faulty in FH v6 with Unicode chars in path
if fhGetAppVersion() == 6 then
local _, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
strParent = strFileName:match("^(.+)[\\/][^\\/]+[\\/]?$")
end
end
return strParent
end -- function GetParentFolder
-- Check if file exists --
function fh.FlgFileExists(strFileName)
-- strFileName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FileExists(strFileName)
end -- function FlgFileExists
-- Check if folder exists --
function fh.FlgFolderExists(strFolderName)
-- strFolderName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FolderExists(strFolderName)
end -- function FlgFolderExists
-- Delete a file if it exists --
function fh.DeleteFile(strFileName,errFunction)
-- strFileName ~ full file path
-- errFunction ~ optional error reporting function
-- return value ~ true if file does not exist or is deleted else false
if fh.FSO:FileExists(strFileName) then
fh.FSO:DeleteFile(strFileName,true)
if fh.FSO:FileExists(strFileName) then
doError("File Not Deleted:\n"..strFileName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFile
-- Delete a folder if it exists including contents --
function fh.DeleteFolder(strFolderName,errFunction)
-- strFolderName ~ full folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder does not exist or is deleted else false
if fh.FSO:FolderExists(strFolderName) then
fh.FSO:DeleteFolder(strFolderName,true)
if fh.FSO:FolderExists(strFolderName) then
doError("Folder Not Deleted:\n"..strFolderName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFolder
-- Rename a file if it exists --
function fh.RenameFile(strFileName,strNewName)
-- strFileName ~ full file path
-- strNewName ~ new file name & type
-- return value ~ true if file exists but new name does not and rename is OK else false
local strNewFile = fh.GetParentFolder(strFileName).."\\"..strNewName
if fh.FSO:FileExists(strFileName) and not fh.FSO:FileExists(strNewFile) then
local fileObject = fh.FSO:GetFile(strFileName)
fileObject.Name = strNewName
if fh.FSO:FileExists(strNewFile) then
return true
end
end
return false
end -- function RenameFile
-- Rename a folder if it exists --
function fh.RenameFolder(strFolderName,strNewName)
-- strFolderName ~ full folder path
-- strNewName ~ new folder name
-- return value ~ true if folder exists but new name does not and rename is OK else false
local strNewFolder = fh.GetParentFolder(strFolderName).."\\"..strNewName
if fh.FSO:FolderExists(strFolderName) and not fh.FSO:FolderExists(strNewFolder) then
local folderObject = fh.FSO:GetFolder(strFolderName)
folderObject.Name = strNewName
if fh.FSO:FolderExists(strNewFolder) then
return true
end
end
return false
end -- function RenameFolder
-- Copy a file if it exists and destination is not a folder --
function fh.CopyFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
fh.FSO:CopyFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
return false
end -- function CopyFile
-- Copy a folder if it exists and destination is not a file --
function fh.CopyFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
fh.FSO:CopyFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
return false
end -- function CopyFolder
-- Move a file if it exists and destination is not a folder --
function fh.MoveFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
if fh.DeleteFile(strDestination) then
fh.FSO:MoveFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFile
-- Move a folder if it exists and destination is not a file --
function fh.MoveFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
if fh.DeleteFolder(strDestination) then
fh.FSO:MoveFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFolder
local function CreateFolder(strFolderName) -- V3.3
fh.FSO:CreateFolder(strFolderName)
end -- local function CreateFolder(strFolderName)
-- Make subfolder recursively if does not exist --
function fh.MakeFolder(strFolderName,errFunction)
-- strFolderName ~ full source folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder exists or created else false
if not fh.FSO:FolderExists(strFolderName) then
if #strFolderName > 4 -- V3.3
and not fh.MakeFolder(fh.GetParentFolder(strFolderName),errFunction) then
return false
end
if not pcall(CreateFolder,strFolderName) -- V3.3
or not fh.FSO:FolderExists(strFolderName) then
doError("Cannot Make Folder Path: \n"..strFolderName.." \n",errFunction)
return false
end
end
return true
end -- function MakeFolder
-- Check if folder writable --
function fh.FlgFolderWrite(strFolderName)
-- strFolderName ~ full source folder path
-- return value ~ true if folder writable else false
if fh.FlgFolderExists(strFolderName) then
if fh.MakeFolder(strFolderName.."\\vwxyz") then
fh.FSO:DeleteFolder(strFolderName.."\\vwxyz",true)
return true
end
end
return false
end -- function FlgFolderWrite
-- Open File with ANSI path and return Handle --
function fh.OpenFile(strFileName,strMode)
-- strFileName ~ full file path
-- strMode ~ "r", "w", "a" optionally suffixed with "+" &/or "b"
-- return value ~ file handle
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(strContents,strFileName,strFormat)
-- strContents ~ text string
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ true if successful else false
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhSaveTextFile(strFileName,strContents,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
local fileHandle = fh.OpenFile(strAnsi,"w")
fileHandle:write(strContents)
assert(fileHandle:close())
if not wasAnsi then
fh.MoveFile(strAnsi,strFileName)
end
return true
end -- function SaveStringToFile
-- Load string from file --
function fh.StrLoadFromFile(strFileName,strFormat)
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ file contents
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhLoadTextFile(strFileName,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
fh.CopyFile(strFileName,strAnsi)
end
local fileHandle = fh.OpenFile(strAnsi,"r")
local strContents = fileHandle:read("*all")
assert(fileHandle:close())
return strContents
end -- function StrLoadFromFile
-- Returns the Path, Filename, and Extension as 3 values --
function fh.SplitFilename(strFileName)
-- strFileName ~ full file path
-- return values ~ path, name.type, type
if fh.FSO:FolderExists(strFileName) then
local strPath = strFileName:gsub("[\\/]$","")
return strPath.."\\","",""
end
strFileName = strFileName.."."
return strFileName:match("^(.-)([^\\/]-%.([^\\/%.]-))%.?$")
end -- function SplitFilename
-- Convert dd/mm/yyyy hh:mm:ss format to integer seconds -- (DateTime format is used in attributes returned by GetFolderContents and DirTree below)
function fh.IntTime(strDateTime)
-- strDateTime ~ date time string
-- return value ~ integer seconds since 01/01/1970 00:00:00
local strDay,strMonth,strYear,strHour,strMin,strSec = strDateTime:match("^(%d%d)/(%d%d)/(%d+) (%d%d):(%d%d):(%d%d)")
if tonumber(strYear) < 1970 then return 0 end
local isDST = false
if tonumber(strMonth) > 4 and tonumber(strMonth) < 11 then isDST = true end -- Approximation is sometimes wrong
local intTime = os.time( { year=strYear; month=strMonth; day=strDay; hour=strHour; min=strMin; sec=strSec; isdst=isDST; } )
local tblDat = os.date("*t",intTime)
if tblDat.isdst then
intTime = intTime + 3600
isDST = true
end
return intTime
end -- function IntTime
-- Return table of attributes for folder --
local function attribFolder(tblAttr)
-- tblAttr ~ folder attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE only supports the tblAttr.name & tblAttr.path
return { mode="directory"; name=tblAttr.name; path=tblAttr.path; }
end -- local function attribFolder
-- Return table of attributes for file --
local function attribFile(tblAttr)
-- tblAttr ~ file attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE does not support the tblAttr.type, tblAttr.shortname, tblAttr.shortpath & sometimes tblAttr.datecreated
return { mode="file"; name=tblAttr.name; path=tblAttr.path; size=tblAttr.size; modified=tblAttr.datelastmodified; attributes=tblAttr.attributes; }
end -- local function attribFile
-- Return attributes table of all files and folders in a specified folder --
function fh.GetFolderContents(strFolder,doRecurse)
-- strFolder ~ full folder path
-- doRecurse ~ true for recursion
-- return value ~ attributes table
local arrList = {}
if fh.FSO:FolderExists(strFolder) then
local function getFileList(strFolder)
local tblList = fh.FSO:GetFolder(strFolder)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFolder(tblAttr))
if doRecurse then getFileList(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFile(tblAttr))
tblAttr = tblEnum:Next()
end
end
getFileList(strFolder)
end
return arrList
end -- function GetFolderContents
-- Return a Directory Tree entry & attributes on each iteration --
function fh.DirTree(strDir,...)
-- strDir ~ full folder path
-- ... ~ list of folders to omit
-- return value ~ full path, attributes table
local arg = {...}
assert( fh.FSO:FolderExists(strDir), "directory parameter is missing or empty" )
local function yieldtree(strDir)
local tblList = fh.FSO:GetFolder(strDir)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.SubFolders) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"directory"))
local isOK = true
for _,strOmit in ipairs (arg) do
if tblAttr.path:match(strOmit) then -- Omit tree branch
isOK = false
break
end
end
if isOK then yieldtree(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.Files) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"file"))
tblAttr = tblEnum:Next()
end
end
return coroutine.wrap(function() yieldtree(strDir) end)
end -- function DirTree
-- Detect FH V5/6 old library modules and advise removal --
function fh.DetectOldModules()
if fhGetAppVersion() > 6 then
local strPath = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugins\\"
local arrFile = { "compat53.lua"; "ltn12.lua"; "luasql\\sqlite3.dll"; "md5.lua"; "pl\\init.lua"; "socket.lua"; "utf8.lua"; "zip.dll"; }
for _, strFile in ipairs (arrFile) do
if fh.FSO:FileExists(strPath..strFile) then
fhMessageBox("\n Detected some old FH V6 library modules. \n\nPlease remove them by running the plugin: \n\n 'Delete old FH6 Plugin Module Files' \n","MB_OK","MB_ICONEXCLAMATION")
break
end
end
end
end -- function DetectOldModules
if fhGetAppVersion() > 6 then unpack = table.unpack end
-- Invoke FH Shell Execute API --
function fh.DoExecute(strExecutable,...)
-- strExecutable ~ full path of executable
-- ... ~ parameter list and optional error reporting function
-- return value ~ true if successful else false
local arg = {...}
local errFunction = fhMessageBox
if type(arg[#arg]) == 'function' then
errFunction = arg[#arg]
table.remove(arg)
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)
-- datDate ~ date point
-- return value ~ day number
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,"MB_OK","MB_ICONEXCLAMATION")
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
-- return value ~ integer year 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
-- ptrIndi ~ pointer to individual
-- return value ~ pointer to fact
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
-- return values ~ EARLIEST, MID, LATEST 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)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
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)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
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+tablex_v3
@Author: Mike Tate
@Version: 3.1
@LastUpdated: 08 Jan 2022
@Description: A Table Load Save Module.
@V3.1: Cater for full UTF-8 filenames.
@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,_,stransi,wasansi -- V1.2 -- V3.1 -- Added _,stransi,wasansi --!
-- 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
stransi,wasansi = general.FileNameToANSI(filename) -- V3.1 -- Cater for non-ANSI filename --!
file,err = io.open( stransi, "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()
if not ( wasansi ) then -- V3.1 -- Cater for non-ANSI filename --!
general.MoveFile(stransi,filename)
end
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
local stransi,wasansi = general.FileNameToANSI(sfile) -- V3.1 -- Cater for non-ANSI filename --!
if not ( wasansi ) then
general.CopyFile(sfile,stransi)
end
tables,err = loadfile( stransi )
if not ( wasansi ) then
general.DeleteFile(stransi) -- V3.1 -- Cater for non-ANSI filename --!
end
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+encoder_v3
@Author: Mike Tate
@Version: 3.6
@LastUpdated: 27 Aug 2024
@Description: Text encoder module for HTML XHTML XML URI UTF8 UTF16 ISO CP1252/ANSI character codings.
@V3.6: In fh.FileLines(...) cater for empty file;
@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","MB_OK","MB_ICONEXCLAMATION")
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) or "" -- Read first lump from file and cater for empty 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" and #strText > 0 then -- Define ANSI conversion to current encoding and cater for empty file
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: 4.2
@LastUpdated: 07 Oct 2024
@Description: Graphical User Interface Library Module
@V4.2: Skip if standalone GEDCOM in fh.SaveSettings() and getDataFiles();
@V4.1: CheckVersionInStore() save & retrieve latest version in file; Remove old wiki Help features;
@V4.0: Cater for full UTF-8 filenames;
@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
--]]
-- 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")
iup.SetGlobal("UTF8MODE_FILE","NO") -- V4.0
function fh.SetUtf8Mode() -- Set IUP into UTF-8 mode
if iupVersion == "3.5" or stringx.encoding() == "ANSI" then return false end
iup.SetGlobal("UTF8MODE","YES")
iup.SetGlobal("UTF8MODE_FILE","YES") -- V4.0
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 = {}
local strClip = general.StrLoadFromFile(strFileName) --! -- V4.0
for strLine in strClip:gmatch("[^\r\n]+") do --! -- V4.0
if #tblField == 0
and strLine:match("^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
else
for i,j in pairs (tblClipData) do
tblClipData[i] = nil --! Restore defaults and clear any junk -- V4.0
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 #strFileName > 0 then -- Skip for standalone GEDCOM -- V4.2
if type(table.save) == "function" then -- Save entire Settings File table per Project/User/Machine
table.save(tblClipData,strFileName)
else
local tblClip = {}
for strKey,strVal in pairs(tblClipData) do -- Else save Settings File lines with key & val fields -- V4.0
table.insert(tblClip,strKey.."="..strVal.."\n") --! -- V4.0
end
local strClip = table.concat(tblClip,"\n") --! -- V4.0
if not general.SaveStringToFile(strClip,strFileName) then
error("\nSettings file not saved successfully.\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.")
end
end
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("%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 not strFrame:match("dialog") -- Inhibit MainLoop if "dialog" mode -- V4.1
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
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
local strErrFile = 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(strErrFile) -- 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. ","MB_OK","MB_ICONEXCLAMATION")
end
general.SaveStringToFile(strErrFile,strErrFile) -- Update file modified time
else
general.DeleteFile(strErrFile) -- Delete file if Internet is OK
if strReturn ~= nil then
strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits
general.SaveStringToFile(strVersion,strFile) -- Update file modified time and save version -- V4.1
end
end
else
strVersion = general.StrLoadFromFile(strFile) -- Retrieve saved latest version -- V4.1
if #strVersion > 9 then general.DeleteFile(strFile) end
end
end
return strVersion or "0"
end -- function VersionInStore
local function intVersion(strVersion) -- Convert version string to comparable integer
local intVersion = 0
local arrNumbers = {}
strVersion:gsub("(%d+)", function(strDigits) table.insert(arrNumbers,strDigits) end) -- V4.1
for i=1,5 do
intVersion = intVersion * 100 + tonumber(arrNumbers[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 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 strDataFile
end -- local function getPluginDataFileName
local function getDataFiles(strScope) -- Compose the Plugin Data file & path & root names
local strPluginName = fh.Plugin
local strPluginPlain = stringx.plain(strPluginName)
local strDataRoot = "" -- Plugin data file root name -- V4.2
local strDataPath = "" -- Plugin data folder path name
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 > 0 then -- Standalone GEDCOM path is ""
strDataPath = strDataFile:gsub("\\"..strPluginPlain.."%.[D,d][A,a][T,t]$","")
strDataRoot = strDataPath.."\\"..strPluginName
general.MakeFolder(strDataPath) -- V3.4
end
return strDataFile, strDataPath, strDataRoot
end -- local function getDataFiles
function fh.Initialise(strVersion,strPlugin) -- Initialise the GUI module with optional Version & Plugin name
local strAppData = 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 ""),"MB_OK","MB_ICONEXCLAMATION")
end
else
fhMessageBox("\nPlugin has not been saved!","MB_OK","MB_ICONEXCLAMATION")
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 = fhGetContextInfo("CI_PROJECT_DATA_FOLDER") -- Paths used by Load/SaveFolder for relative folders -- V4.0
fh.PublicPath = fhGetContextInfo("CI_PROJECT_PUBLIC_FOLDER") -- Public data folder path name -- V4.0
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"
local TblOption = {} -- Table of GUI sticky options
local TblMode = {} -- Table of Modes of GEDCOM Export
local TblChar = {} -- Table of Character Encodings
local TblObje = {} -- Table of Media Object options
local TblPriv = {} -- Table of [[private]] text options
local TblRich = {} -- Table of rich text format options
local TblName = {} -- Table of part/full frame naming options
local TblLabs = {} -- Table of default synthetic Note labels, etc
local ArrOptionToggles = { "Tidy"; "Fact"; "Pref"; "Capt"; "Make"; } -- Array of ON/OFF option toggle names -- V5.0
-- Preset Global Data Definitions --
function PresetGlobalData()
iup_gui.Gap = "2"
iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V4.0
IsIupUtf8 = iup_gui.SetUtf8Mode()
general.DetectOldModules() -- V5.1
IntFhVersion = fhGetAppVersion()
StrLessLess = "«" -- Legacy 9 rich text delimiters and to hide format style codes -- V5.3
StrMoreMore = "»"
StrBullet = "•" -- Bullet Blob for lists
StrSection = "§" -- Section Sign is @ substitute
StrPilcrow = " ¶ " -- Newline Pilcrow symbol
if IntFhVersion > 5 then
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
local arrStyle = { -- Define style codes for doSrcTempLnk() Reformat Metafields -- V5.3
{ Val=32; Pre='"' ; Suf='"' ; };
{ Val=16; Pre="'" ; Suf="'" ; };
{ Val= 8; Pre=""; Suf=" "; }; -- Each Windows Registry settings bit enables a style code
{ Val= 4; Pre=""; Suf=""; };
{ Val= 2; Pre=""; Suf=""; };
{ Val= 1; Pre=""; Suf=""; };
}
local strRegKey = iup_gui.GetRegKey("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\PROCESSOR_ARCHITECTURE")
local StrProcArch64 = not ( strRegKey == "x86" ) -- 64-bit or 32-bit PC for WOW6432Node support
local strKeyHKLM = "HKLM\\SOFTWARE\\" -- HKEY_LOCAL_MACHINE keys path
if StrProcArch64 then -- Insert WOW6432Node for 64-bit PC
strKeyHKLM = "HKLM\\SOFTWARE\\WOW6432Node\\"
end
strKeyHKLM = strKeyHKLM.."Calico Pie\\Family Historian\\2.0\\Preferences\\Report Footnote Style "
local intBookStyle = iup_gui.GetRegKey(strKeyHKLM.."Book") -- Tools > Preferences > Sources > 'Book' Style
local intTitlStyle = iup_gui.GetRegKey(strKeyHKLM.."Title") -- Tools > Preferences > Sources > 'Title' Style
local strBookStyle = "{%1}"
local strTitlStyle = "{%1}"
if intBookStyle and intTitlStyle then -- V5.4
for _, dicStyle in ipairs (arrStyle) do -- Compose style codes chosen by Windows Registry settings in FH V7
local intVal = dicStyle.Val
if intBookStyle - intVal >= 0 then
intBookStyle = intBookStyle - intVal
strBookStyle = dicStyle.Pre..strBookStyle..dicStyle.Suf
end
if intTitlStyle - intVal >= 0 then
intTitlStyle = intTitlStyle - intVal
strTitlStyle = dicStyle.Pre..strTitlStyle..dicStyle.Suf
end
end
end
StrBookStyle = "<"..strBookStyle..">" -- 'Book' Style gsub replacement code
StrTitlStyle = "<"..strTitlStyle..">" -- 'Title' Style gsub replacement code
StrHideStyle = StrLessLess.."%1"..StrMoreMore -- Hide style gsub replacement code
StrMatchPref = StrLessLess.."([ibcu])"..StrMoreMore.." ?" -- Match style prefix gsub code
StrMatchSuff = " ?"..StrLessLess.."(/[ibcu])"..StrMoreMore -- Match style suffix gsub code
StrReformat = "" -- Closing message when Reformat Metafields is used
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
-- Current selected GEDCOM destination globals:
IntMode = 1 -- Index into TblMode
StrFull = "+" -- "+" Full / "-" Brief mode
StrAbbr = "Std" -- Abbreviated mode name
StrMode = "Std+" -- Abbr .. "+" Full / "-" Brief mode for current rules
StrName = StrAbbr -- Same as StrAbbr but for choosing default modes (only needed for CEA/B)
StrNode = StrMode -- Same as StrMode but for choosing default rules (only needed for CEA/B)
IntNode = IntMode -- Same as IntMode but for choosing various modes (only needed for CEA/B)
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/Other Options tab; @ handling; -- V5.0 added Make="OFF/ON";
table.insert(TblMode, { Abbr="Std"; Full="Standard GEDCOM Release 5.5" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; 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" ; Make="OFF"; AtAt="@@"; }) -- V2.8 -- V4.0
table.insert(TblMode, { Abbr="AQP"; Full="Ancestral Quest Program" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.8
table.insert(TblMode, { Abbr="AGS"; Full="Ancestris Genealogy Software" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.8
table.insert(TblMode, { Abbr="ANC"; Full="Ancestry Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V2.2
table.insert(TblMode, { Abbr="BHG"; Full="Behold Genealogy" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V5.1
table.insert(TblMode, { Abbr="FH5"; Full="Family Historian V5.0" ; Tidy="OFF"; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; })
if IntFhVersion > 6 then
table.insert(TblMode, { Abbr="FH6"; Full="Family Historian V6.2" ; Tidy="OFF"; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V4.0
end
table.insert(TblMode, { Abbr="FST"; Full="Family Search Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.2
table.insert(TblMode, { Abbr="FTA"; Full="Family Tree Analyzer" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="FTD"; Full="Family Tree DNA" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="ON" ; AtAt= "@"; }) -- V3.3
table.insert(TblMode, { Abbr="FTL"; Full="Family Tree Maker (legacy)" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V4.0
table.insert(TblMode, { Abbr="FTM"; Full="Family Tree Maker 2019" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V4.0
table.insert(TblMode, { Abbr="FMP"; Full="FindMyPast Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V2.2
table.insert(TblMode, { Abbr="GKP"; Full="GEDKeeper Program" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; Text=10000; }) -- V6.0 -- V6.1 added Text limit where < 1,000,000
table.insert(TblMode, { Abbr="GWC"; Full="GEDmill Website Creator" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.3
table.insert(TblMode, { Abbr="GST"; Full="GedSite" ; Tidy="OFF"; Fact="ON" ; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V3.1
table.insert(TblMode, { Abbr="GSP"; Full="GedStar Pro for Android" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="GRT"; Full="Genes Reunited Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="GFT"; Full="Gramps Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="HER"; Full="Heredis by BSD Concept" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; 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" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="MFT"; Full="My Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt="@@"; }) -- V4.0
table.insert(TblMode, { Abbr="MYH"; Full="MyHeritage Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.2
table.insert(TblMode, { Abbr="RFT"; Full="RootsFinder Family Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.8 -- V4.0 "@@" => "@"
table.insert(TblMode, { Abbr="RMT"; Full="Roots Magic Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="RWW"; Full="RootsWeb WorldConnect" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="TNG"; Full="The Next Generation" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="TPT"; Full="TribalPages Tree" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V3.1
table.insert(TblMode, { Abbr="ZPG"; Full="ZoomPast Genealogy" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; }) -- V2.0
table.insert(TblMode, { Abbr="CEA"; Full="Custom Export Alpha GEDCOM 5.5" ; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
table.insert(TblMode, { Abbr="CEB"; Full="Custom Export Bravo GEDCOM 5.5.1"; Tidy="ON" ; Fact="OFF"; Pref="ON" ; Capt="ON" ; Make="OFF"; AtAt= "@"; })
for intMode, tblMode in ipairs(TblMode) do
TblMode[tblMode.Abbr] = intMode -- Reverse translate abbreviation to integer key -- V5.0
end
TblObje = { } -- Table of Media Object options
table.insert(TblObje, { Abbr="PART~LMO"; Full="Part Frames in Export Folder & Local Media Full File Paths"; Mode=" All " ; }) -- V1.8 revised options
table.insert(TblObje, { Abbr="FULL~LMO"; Full="Full Images in Export Folder & Local Media Full File Paths"; Mode=" " ; })
table.insert(TblObje, { Abbr= "ALL~LMO"; Full="Part & Full in Export Folder & Local Media Full File Paths"; Mode=" MYH " ; }) -- V3.2 MYH
table.insert(TblObje, { Abbr="PART~ABS"; Full="Part Frames in Export Folder & Media Record Full File Paths"; Mode=" " ; })
table.insert(TblObje, { Abbr="FULL~ABS"; Full="Full Images in Export Folder & Media Record Full File Paths"; Mode=" " ; })
table.insert(TblObje, { Abbr= "ALL~ABS"; Full="Part & Full in Export Folder & Media Record Full File Paths"; Mode=" FTL FTM GKP " ; }) -- V4.0 FTL -- V6.0 GKP
table.insert(TblObje, { Abbr="FILE~REL"; Full="Use Existing Media Files with Media Record Existing Paths" ; Mode=" FH5 FH6 " ; }) -- V4.0 FH6
table.insert(TblObje, { Abbr="FILE~ABS"; Full="Use Existing Media Files with Media Record Full File Paths"; Mode=" GST " ; }) -- V3.1
table.insert(TblObje, { Abbr="WIPE~ALL"; Full="Exclude all the Multimedia Files and Media Records 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
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 All " ; }) -- 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=" " ; })
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
table.insert(TblLabs, { Name="Principal" ; Help="Principal Witness Hyperlink" ; Tail="" ; }) -- V5.6 See below
table.insert(TblLabs, { Name="Family" ; Help="Principal Family Hyperlink" ; Tail="" ; }) -- V5.6 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
table.insert(TblLabs, { Name="Principal" ; Help="Principal Witness Hyperlink" ; Tail="" ; }) -- V5.6 See above
table.insert(TblLabs, { Name="Family" ; Help="Principal Family Hyperlink" ; Tail="" ; }) -- V5.6 See above
end
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="Original Date" ; Help="(DATE) Original Fact Date" ; }) -- V3.3 -- V5.5 moved
table.insert(TblLabs, { Name="Record Flag" ; Help="(_FLGS) Record Flag" ; }) -- V4.0 -- V5.5 moved
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="Footnote" ; Help="Footnote citation" ; }) -- V5.5
table.insert(TblLabs, { Name="Short Footnote" ; Help="Short Footnote citation" ; }) -- V5.5
table.insert(TblLabs, { Name="Bibliography" ; Help="Bibliography citation" ; }) -- V5.5
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
if IntFhVersion > 6 then
table.insert(TblLabs, { Name="Weblink" ; Help="Rich Text URL Weblink" ; Tail=": "; }) -- V5.3
table.insert(TblLabs, { Name="Record" ; Help="Rich Text Record Link" ; Tail=": "; }) -- V5.3
end
ResetResultSet()
end -- function PresetGlobalData
function StrGedcomExportMode(strAbbr,strFull) -- Handle GEDCOM Export mode and Full option -- V3.3 -- V5.0
-- strAbbr ~ Abbreviation for GEDCOM destination (optional)
-- strFull ~ "+" Full / "-" Brief mode selection (optional)
IntMode = TblMode[strAbbr] or TblOption.Mode -- Set global IntMode, StrAbbr, StrFull, StrMode, StrName, StrNode, IntNode
if type(IntMode) ~= "number" then
--? fhMessageBox("StrGedcomExportMode ~ IntMode = "..(IntMode or "nil"))
IntMode = TblMode[(IntMode or "Std")] -- Reverse translate abbreviation to integer key -- V5.0
end
StrAbbr = TblMode[IntMode].Abbr
StrFull = strFull or "+"
if not strFull and TblOption[StrAbbr] then
StrFull = TblOption[StrAbbr].Full or "+"
end
StrMode = StrAbbr..StrFull
StrName = TblOption[StrAbbr].Mode or StrAbbr
StrNode = StrName..StrFull
IntNode = TblMode[StrName]
end -- function StrGedcomExportMode
-- 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
-- Set per GEDCOM destination option setting -- -- V5.0
function SetPerModeOption(strOption,tblOption)
-- strOption ~ TblOption key: "Obje" , "Char" , "Priv" , "Rich"
-- tblOption ~ Options table: TblObje, TblChar, TblPriv, TblRich
for intOpt, tblOpt in ipairs ( tblOption ) do -- Search per Mode option settings -- V4.0
if tblOpt.Mode:match(" All ")
or tblOpt.Mode:match(" "..StrName.." ")
or tblOpt.Mode:matches(StrNode) then -- Option matches Mode of GEDCOM Export -- V4.0
TblOption[StrMode][strOption] = intOpt
if not tblOpt.Mode:match(" All ") then break end -- Break if matches Mode of GEDCOM Export
end
end
end -- function SetPerModeOption
-- Reset Options for Mode of GEDCOM Export --
function ResetGedcomOptions(tblMode,strFull,strNoToggles)
-- tblMode ~ TblMode entry for any GEDCOM destination
-- strFull ~ "+" Full or "-" Brief mode (optional)
-- strNoToggles ~ true inhibits toggle reset (optional)
local arrFull = { "+"; "-"; }
StrAbbr = tblMode.Abbr -- V3.3 -- V5.0
if strFull then arrFull = { strFull; } end
TblOption[StrAbbr] = TblOption[StrAbbr] or { } -- V5.0
TblOption[StrAbbr].Full = strFull or "+" -- V3.3
for _, strFull in ipairs ( arrFull ) do
StrGedcomExportMode(StrAbbr,strFull) -- V5.0
if not TblOption[StrMode] then TblOption[StrMode] = { } end -- V4.0
if not strNoToggles then -- Inhibit for Basic Options tab Reset Options button -- V4.0
local tblMode = TblMode[IntNode]
for _, strItem in ipairs ( ArrOptionToggles ) do
TblOption[StrMode][strItem] = tblMode[strItem] -- Default Tidy Names & Fact Set & Synth Prefix & Repeat Caption & Make DNA Birth/Death settings -- V3.3
end
end
SetPerModeOption("Obje",TblObje) -- Per Mode Media Conversion option setting -- V5.0
SetPerModeOption("Char",TblChar) -- Per Mode Character Encode option setting -- V5.0
SetPerModeOption("Priv",TblPriv) -- Per Mode [[private]] text option setting -- V3.2
SetPerModeOption("Rich",TblRich) -- Per Mode rich text format option setting -- V4.0
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 _, 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
-- 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
local strOptionPath = TblOption.Path
TblOption = { }
TblOption.Path = strOptionPath
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
StrGedcomExportMode() -- V3.3 -- V5.0
TblOption.Obje = TblOption[StrMode].Obje -- Default Media Object option setting
TblOption.Char = TblOption[StrMode].Char -- Default Character Encoding 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.Seps = TblOption.Seps or "OFF" -- Default folder path separator option -- V6.0
TblOption.Keep = TblOption.Keep or "OFF" -- Default keep Media subfolder option -- V2.9
TblOption.Proj = TblOption.Proj or "ON" -- Default for where settings are held -- V5.0
local strPath = general.SplitFilename(StrImport) -- Project GEDCOM file import folder path
StrExportOld = strPath:gsub("\\$","\\Export")
StrExportNew = StrExportOld -- V5.1
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.."\\"
StrExportNew = strPath:gsub("\\$","\\Export") -- Project default GEDCOM file export folder path -- V5.1
TblOption.Path = StrExportNew
else
TblOption.Path = TblOption.Path or StrExportNew -- Standalone GEDCOM defaults to subsidiary Export folder -- V5.1
end
end -- function ResetDefaultSettings
-- Convert textual abbreviation to droplist integer key -- -- V5.0
function ConvertAbbrToKey(strOption,tblOption)
-- strOption ~ TblOption key: "Mode" , "Obje" , "Char" , "Priv" , "Rich"
-- tblOption ~ Options table: TblMode, TblObje, TblChar, TblPriv, TblRich
local anyOption = TblOption[strOption]
if type(anyOption) == "string" then
for intOpt, tblOpt in ipairs ( tblOption ) do -- Convert textual abbreviation to integer key
if anyOption == tblOpt.Abbr then
anyOption = intOpt
break
end
end
if type(anyOption) == "string" then anyOption = 1 end -- Conversion failed so default to 1
end
TblOption[strOption] = anyOption
end -- function ConvertAbbrToKey
-- Load Sticky Settings from File --
function LoadSettings(strProject)
-- strProject ~ "ON" or "OFF" to set where settings are held
if not strProject then -- Only load GUI settings for initial case
iup_gui.LoadSettings("Bars") -- Includes "Main","Font","Bars" dialogues and "FontSet" & "History"
end
local strScope = "Project"
if strProject == "OFF" then -- V5.0
strScope = "Machine" -- V5.0
end
TblOption = iup_gui.LoadGlobal("Option",TblOption,strScope) -- Load local Project settings by default unless strProject is OFF
TblOption.Proj = strProject or TblOption.Proj or "ON"
if TblOption.Proj == "OFF" and strScope ~= "Machine" then
strScope = "Machine"
TblOption = iup_gui.LoadGlobal("Option",TblOption,strScope) -- V5.0
end
TblOption.Path = iup_gui.LoadFolder("Path",StrExportNew,strScope) -- 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","")),"MB_OK","MB_ICONEXCLAMATION")
end
if type(TblOption.Mode) == "string" then -- V5.0 Legacy fixes for Mode options
if TblOption.Mode == "GSW" then TblOption.Mode = "GST" end -- V3.1 Legacy Fix
if TblOption.Mode == "GSB" then TblOption.Mode = "GST" end -- V3.1 Lagacy Fix
if TblOption.Mode == "Sdr" then TblOption.Mode = "Str" end -- V4.0 Standard Release 5.5.1 Legacy 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
end
if type(TblOption.Priv) == "string" then -- Legacy fixes for Priv options
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
end
ConvertAbbrToKey("Mode",TblMode) -- Convert textual Mode option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Obje",TblObje) -- Convert textual Obje option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Char",TblChar) -- Convert textual Char option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Priv",TblPriv) -- Convert textual Priv option from Abbr to integer key -- V5.0
ConvertAbbrToKey("Rich",TblRich) -- Convert textual Rich option from Abbr to integer key -- V5.0
local strHistory = iup_gui.History
if #strHistory > 2 then -- Perform legacy adjustments depending on historical version
if strHistory <= " 1.7 " then
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","")),"MB_OK","MB_ICONEXCLAMATION")
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 _, tblMode in ipairs ( TblMode ) do
ResetGedcomOptions(tblMode) -- Reset options for each Mode of GEDCOM Export (early legacy) -- V3.3
end
StrGedcomExportMode() -- V3.3 -- V5.0
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 _, tblMode in ipairs ( TblMode ) do -- Per Mode Media Object option setting
StrGedcomExportMode(tblMode.Abbr) -- V3.3 -- V5.0
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 _, 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 _, 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 _, 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 "+"
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] or {} -- V5.0
TblOption[strAbbr].Full = strFull
end
end
end
end
for _, tblMode in ipairs ( TblMode ) do -- Adjust old options to include new options
local strAbbr = tblMode.Abbr
TblOption[strAbbr] = TblOption[strAbbr] or {} -- V5.0
for _, strFull in ipairs ( { "+"; "-"; } ) do -- All abbreviated Modes and Full/Brief option -- V4.0
StrGedcomExportMode(strAbbr,strFull) -- V5.0
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
SetPerModeOption("Priv",TblPriv) -- Per Mode [[private]] text option setting -- V3.2
end
if not TblOption[StrMode].Rich then
SetPerModeOption("Rich",TblRich) -- Per Mode rich text format option setting -- V4.0
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
local tblMode = TblMode[IntNode] -- V5.0
if not TblOption[StrMode][strItem] then
TblOption[StrMode][strItem] = tblMode[strItem] -- Initialise Tidy Names & Fact Set & Synth Prefix & Repeat Caption & Make DNA Birth/Death 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
general.MakeFolder(TblOption.Path,iup_gui.MemoDialogue) -- Ensure that the Export folder exists -- V5.1
SaveSettings() -- Save sticky data settings
end -- function LoadSettings
-- Adjust Option integer key to Abbr string -- -- V5.0 -- Save key as abbreviation to cater for droplist changes plus defensive adjustments
function IntAdjustOption(strOption,tblOption)
-- strOption ~ TblOption key: "Mode" , "Obje" , "Char" , "Priv" , "Rich"
-- tblOption ~ Options table: TblMode, TblObje, TblChar, TblPriv, TblRich
ConvertAbbrToKey(strOption,tblOption) -- Convert erroneous textual abbreviation to integer key
local intOption = TblOption[strOption] or 1 -- Should be integer key of droplist option
if type(intOption) == "number" then
intOption = math.floor( math.max(intOption,1) ) -- Ensure integer is within range of droplist keys
intOption = math.floor( math.min(intOption,#tblOption) )
TblOption[strOption] = tblOption[intOption].Abbr -- Convert to abbreviation for save to cater for droplist changes
end
return intOption
end -- function IntAdjustOption
-- Save Sticky Settings to File --
function SaveSettings(strProject)
-- strProject ~ "ON" or "OFF" to set where settings are held
local intMode = IntAdjustOption("Mode",TblMode) -- Save the Mode option using Abbr rather than integer key -- V5.0
local intObje = IntAdjustOption("Obje",TblObje) -- Save the Obje option using Abbr rather than integer key -- V5.0
local intChar = IntAdjustOption("Char",TblChar) -- Save the Char option using Abbr rather than integer key -- V5.0 -- Eliminates non-BOM options
local intPriv = IntAdjustOption("Priv",TblPriv) -- Save the Priv option using Abbr rather than integer key -- V5.0
local intRich = IntAdjustOption("Rich",TblRich) -- Save the Rich option using Abbr rather than integer key -- V5.0
local strScope = "Project"
strProject = strProject or TblOption.Proj or "ON"
if strProject == "OFF" then -- V5.0
strScope = "Machine" -- V5.0
end
iup_gui.SaveGlobal("Option",TblOption,strScope) -- V5.0
iup_gui.SaveFolder("Path",TblOption.Path,strScope) -- V1.6
iup_gui.SaveSettings("Bars") -- Includes "Main","Font","Bars" dialogues and "FontSet" & "History"
TblOption.Mode = intMode
TblOption.Obje = intObje
TblOption.Char = intChar -- V5.0
TblOption.Priv = intPriv
TblOption.Rich = intRich
StrGedcomExportMode() -- Set IntMode, StrAbbr, StrMode, StrName, StrNode, IntNode, StrFull -- V5.0
StrZipFMP = TblOption.Path.."\\ZipFMP\\" -- FMP folder for ZIP bulk Media files -- V3.3
end -- function SaveSettings
function doCopyFile(strSource,strExport) -- Copy media file and modification date-time; used by doMediaError(), doMakeFile(), doFileLink(), doUniqueId(), ExportGedcom()
-- strSource ~ Source file path
-- strExport ~ Export file path
local isOk, strErr = pcall(general.CopyFile,strSource,strExport) -- V5.0 -- V5.2 -- Trap and report errors
if not isOk then
fhMessageBox("\nCopy File Error\n"..(strErr or "File not copied").."\nSource File:\n"..strSource.."\nExport File:\n"..strExport.."\n","MB_OK","MB_ICONEXCLAMATION")
fhSleep(1000,100)
end
end -- function doCopyFile
function HandleNonIntegerOption(strOption) -- Check and report non-integer options -- V5.0
-- strOption ~ TblOption key: "Obje" , "Char" , "Priv" , "Rich"
if type(TblOption[strOption]) ~= "number" then
--? fhMessageBox("SetSharedData() TblOption."..strOption.." = "..(TblOption[strOption] or "nil"),"MB_OK","MB_ICONEXCLAMATION") --?
TblOption[strOption] = 1
end
end -- function HandleNonIntegerOption
function SetSharedData() -- Shared variables for MakeRules(), setRules() & 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
StrGedcomExportMode() -- GEDCOM Export mode Mode = Std+, FH5+, FTM-, FMP-, etc, Abbr = Std, FH5, FTM, TNG, etc, Full = + or - -- V3.3
HandleNonIntegerOption("Obje")
HandleNonIntegerOption("Char") -- Defensive corrections needed? -- V5.0
HandleNonIntegerOption("Priv")
HandleNonIntegerOption("Rich")
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
strChar = TblChar[TblOption.Char].Abbr -- GEDCOM character encoding option ANSI, UTF8, UTF16, etc
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[IntNode].AtAt -- GEDCOM "@@" to single "@" or "§" or keep, as many programs do not honour GEDCOM "@@" convention, only for setRules() and doPrune() -- V3.3 -- V1.9 -- V5.0 IntNode instead of TblOption.Mode
intText = TblMode[IntNode].Text or 1000000 -- Set the long text field maximum character length -- V6.1
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 doUniqueId() -- 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"
and StrName ~= "FH5" and StrName ~= "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
isMake = ( TblOption[StrMode].Make == "ON" ) -- Make missing Birth/Death events for DNA products, for doUniqueId(), etc -- V5.0
arrMake = {} -- Table of facts with Normal Time Frame = Soon After Birth and Post_Death -- V5.0
intFontSize, strFontName = (iup_gui.GetRegKey("HKCU\\Software\\Calico Pie\\Family Historian\\2.0\\Preferences\\PDX Font") or ""):match("^(%d+),[%d,]+(.-)$") -- V5.7
intFontSize = ( intFontSize or 200 ) / 20 -- Default font size and name -- V4.5
end -- function SetSharedData
function MakeRules() -- Prototype for conversion Rules
SetSharedData() -- V4.0 -- V5.0 -- Use IntMode, StrAbbr, StrMode, StrName, StrNode, IntNode, StrFull below
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 Text from Source or 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 strFormat = nil -- Format of Media set by doMakeFile() due to TblOption.Jpeg == "ON" -- V5.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; -4 = String convert error; -5 = Extremely long text;
-- 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
if intRule == -5 then strItem = "Extremely Long Text Check" end -- V6.1
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
-- Adapt to use lookup table indexed by StrMode if other products need hyperlinks
local function strHyperlinkInd(strRecId) -- Create TNG hyperlink for Individual -- V5.6
return ''..strRecordName(strRecId)..''
end -- local function strHyperlinkInd
local function strHyperlinkFam(strRecId) -- Create TNG hyperlink for Family -- V5.6
local strFam = dicLabel["Family"] -- Translate 'Family' label -- V5.6
if #strFam == 0 then return "" end
return ''..strFam..' of '
--! return '..(strRecordName(strRecId):gsub("... of ",""))..'
end -- local function strHyperlinkFam
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).."..."
elseif strRecId:match("^[IF]")
and strTree and StrAbbr == "TNG" then -- Use hyperlink in TNG Witness Role notes -- V5.6
-- John SMITH
-- John SMITH and Jane DOE
if strRecId:match("^I") then
return strHyperlinkInd(strRecId) -- Individual hyperlink -- V5.6
end
if strRecId:match("^F") then
local strHref = strHyperlinkFam(strRecId) -- Family hyperlink with Husband & Wife hyperlinks -- V5.6
local strPref = ""
local ptrFam = fhNewItemPtr()
ptrFam:MoveToRecordById("FAM",tonumber(strRecId:match("%d+")))
for _, strTag in ipairs ({ "~.HUSB[1]>"; "~.WIFE[1]>"; "~.HUSB[2]>"; "~.WIFE[2]>"; }) do
local ptrTag = fhGetItemPtr(ptrFam,strTag) -- Cater for none, single, and same sex parents -- V5.6
if ptrTag:IsNotNull() then
strRecId = "I"..fhGetRecordId(ptrTag)
strHref = strHref..strPref..strHyperlinkInd(strRecId)
strPref = " & "
end
end
return strHref
end
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
if tblRecord[intLine] then
table.insert(tblRecord,intLine,strText) -- Insert text into record array
else
tblRecord[intLine] = strText -- Append text onto record array -- V5.3
end
if tblLineNo[intLine] then
table.insert(tblLineNo,intLine,intOrig) -- Insert import line number of current line -- V4.0
else
tblLineNo[intLine] = intOrig -- Append import line number of current line -- V5.3
end
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"; };
_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"; };
_UID = { Name="#) Unique Id:\t"; };
_FSID = { Name="#) FamilySearch Id:\t";}; -- V5.0
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 dicErr = {} -- List of inhibited error messages -- V4.5
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 intChar = 0 -- Characters in long root line -- V5.3
local arrLong = {} -- Accumulate a long root line -- V5.3
repeat
intLine = intLine + 1
local strLine = tblRecord[intLine] -- Get next line from record
local intLev, strTag, strVal = getParts(strLine)
intChar = intChar + #strVal
if intChar > (intText * 1.1) then strVal = "" end -- Ignore tail of excessively long text -- V5.3 -- V6.1
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
table.insert(arrLong,"\r"..strVal) -- Append CONTinuation text after return -- V5.3
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
table.insert(arrLong,strVal) -- Apend CONCatenation or _TEXT text -- V5.3
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
table.insert(arrLong,"\n_FMT") -- Apend _FMT tag -- V4.3 -- V5.3
else
if #arrLong > 1 then
tblRecord[intLine-1] = table.concat(arrLong) -- Update long root line
arrLong = {}
end
arrLong[1] = strLine -- Initial long text line -- V5.3
intChar = 0
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
local intChar = 0
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(" ")
or (strText:byte(intMax) or 0) > 127 do -- Avoid CONC break on space or UTF8 character -- V5.3
intMax = intMax - 1
end
if intMax <= 0 then intMax = 254 - #strTag - 2 end -- Cater for 250+ consecutive space/UTF8 chars -- 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
strText = strText:sub(intMax+1) -- Extract tail of long text
intLine = intLine + 1
if intLine % 23 == 0 then
fhSleep(10,8)
collectgarbage("step",0) -- Improves run time! -- V5.3
end
intChar = intChar + #strVal
if intChar > intText then strText = "" end -- Discard tail of long text -- V5.3 -- V6.1
until #strText == 0
if intChar > intText then -- Report very long text -- V5.3 -- V6.1
local strRid,strRec = tblRecord[1]:match("^0 @%u(%d+)@ ([_%u]+)")
local strType = "Note"
if strTag:match("TEXT") then strType = "Text From Source" end
local strText = tostring(intText):gsub("000$",",000"):gsub("000,",",000,") -- V6.1
local strErr = "Extremely long text" -- V6.1
local strMsg = "Extremely long "..strType.." exceeds "..strText.." characters.\n\nExcess "..strType.." text is discarded from the export.\n\nSee "..strRec.." Record Id "..strRid.." and correct the "..strType.."."
if not dicErr[strErr] then
local intKey = iup_gui.MemoDialogue(strMsg.."\n \n Inhibit all similar future messages? \n \n","Yes Inhibit","No Report")
if intKey == 1 then dicErr[strErr] = true end -- Inhibit error message -- V6.1
progbar.Focus()
end
tblRecord.Title = "Text > "..strText.." Chars"
strRec0 = tblRecord[1]
ptrRec0:MoveToRecordById(strRec,strRid)
setResultSet(0,-5,strLine,strMsg:gsub("\n+"," ")) -- Update the Result Set -- V6.1
break
end
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 StrName == "ANC" then -- V5.0 StrName instead of StrAbbr
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
local strWeblink = dicLabel["Weblink"]:gsub("%%","%%%%") -- V5.3
local strRecord = dicLabel["Record"] :gsub("%%","%%%%") -- V5.3
if strRich == "REMOVE" then -- Remove any rich text format codes -- V4.0
-- Web Link or
strVal = strVal:gsub('',strWeblink..'%1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 -- V5.3
strVal = strVal:gsub('',strWeblink..'%1 "%2"') -- V5.3
-- Link to Record or
strVal = strVal:gsub('',strRecord..'%2') -- V5.1 -- V5.3
-- >> Indents to tab
strVal = strVal:gsub('^>+','\t'):gsub('\r>+','\r\t')
-- bold italic underline strike subscript superscript etc deleted
strVal = strVal:gsub('?[biuschaftr].->','')
elseif strRich == "REDUCE" then -- Reduce rich text to basic GedStar Pro & RootsMagic format -- V4.0
-- Web Link or
strVal = strVal:gsub('',strWeblink..'%1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 -- V5.3
strVal = strVal:gsub('',strWeblink..'%1 "%2"') -- V5.3
-- Link to Record or
strVal = strVal:gsub('',strRecord..'%2') -- V5.1 -- V5.3
-- 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('?[chaftr][lsoa].->','') -- deleted
-- * Bullet
strVal = strVal:gsub('^%* ','\t'..StrBullet..' '):gsub('\r%* ','\r\t'..StrBullet..' ')
-- >> Indents to tab
strVal = strVal:gsub('^>+','\t'):gsub('\r>+','\r\t')
if StrName == "GSP" then -- Convert anchor to «?» so < and > are hidden -- V5.0 StrName instead of StrAbbr
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('',strWeblink..'%1 ') -- Omit quotes from URL for FTM, etc. -- V4.6 -- V5.3
strVal = strVal:gsub('',strWeblink..'%1 "%2"') -- V5.3
-- Link to Record or
strVal = strVal:gsub('',strRecord..'%2') -- V5.1 -- V5.3
-- 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('?s>',''):gsub('?[chaftr][lsoa].->','') -- 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)
if not strAny then strAny = strVal end -- V6.4
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 ''..(strRow:gsub('|','
'):gsub('\r','
'))..'
' end) -- V4.3 -- Add
-- Alignment
strAny = strAny:gsub('%s-([^`]-)
', '%1 ') -- V4.3 -- Removed span
strAny = strAny:gsub('%s-([^`]-)
', '%1 ') -- V4.3 -- Removed span
strAny = strAny:gsub('%s-([^`]-)
', '%1 ') -- 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 or
strAny = strAny:gsub('', strRecord..'"%2"') -- V5.1 -- V5.3
-- * 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 = strAny
if intLev >= 0 then -- V6.4
strVal = tostring(intLev).." "..strTag.." "..strAny
end
end
elseif StrName == "GSP" and dicNameTag[strTag] and dicNameTag[strTag].A then -- and LFT?? -- V5.0 StrName instead of StrAbbr
strVal = strVal:gsub('<(.-)>','<%1>') -- Convert attribute value reserved chars < > to HTML escape codes
end
if StrFull == "-" then
strVal = strVal:gsub("[%z\01-\12\14-\31\127]"," ") -- In Brief mode replace invalid control chars with space for strict GEDCOM -- V5.0 \13 = \r must be kept, %z allowed in Lua 5.3
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
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
--[=[ Check if TextPart TIDY differs...
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","MB_OK","MB_ICONEXCLAMATION")
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
local strPre = "" -- V5.0
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, strFnd, strAny, isLink = getParts(strNext)
local strTag = intLev.." "..strFnd -- V5.0
if not isLink and -- Exclude any Record @link@ or UDF @@link@@ -- in V1.8 is this needed ???
( strTag == strNeed or ( strTag == strElse and strPre ~= "_FIELD" ) ) -- V5.0
or intLev < intEnd then
return intNext, strTag -- Return line and tag found
end
strPre = strFnd -- Ensure TEXT not preceded by _FIELD -- V5.0
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( doPrune(strCont) ) -- Handle rich text NOTE, TEXT, etc -- V3.2 -- V5.0
if intLev < intGet then break end -- Escape when no more CONC/CONT/subsidiary tag
if strTag == "_FMT" then
strCont = intPut.." CONT " -- V5.2
elseif 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 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 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
elseif strTag == "PLAC" and strVal:match("%[unknown record%]$") then
break -- Omit synthetic Place citation Note -- V5.2
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 -- V5.6 -- Significant changes for 'Move to Fact Note Min' rule and Labels Set A new labels
-- strWitness ~ Witness link or name
local intLine = tblRecord.Line
local strName = "" -- Witness name
local strWitId = strWitness:match("@([IF]%d+)@") -- [IF] added to cater for Principal Family in synthetic Fact Role -- V2.0
if tblRecord[intLine+1] == "3 ROLE ~Principal~" then -- Handle synthetic Fact Witness Fact -- V5.6
for intType = intLine-1, 1, -1 do
local strType = tblRecord[intType]
local intLev, strTag, strVal = getParts(strType)
if intLev <= 1 then break end -- Quit at start of fact
if strTag == "TYPE" then -- Synthetic fact type found
for intHead = 1, intLine-1 do -- Obtain the RecId of the current record Individual
local strRecId = tblRecord[intHead]:match("^0 @(I%d+)@")
if strRecId then
local dicSHAR = dicRule["2 _SHAR"] or {} -- 'Move to Fact Note Min' rule? -- V5.6
local isOptMin = dicSHAR.Item:match("Move to Fact Note Min")
local strWitId = ""
local intNext = intLine + 2
while intNext < #tblRecord do -- Search through Witnesses after the Principal
local intLev, strTag, strVal = getParts(tblRecord[intNext])
if strTag:match("_SHA[NR]") then -- Name Only or Individual Witness
strWitId = strVal:match("@(I%d+)@") or ""
local intLev, strTag, strVal = getParts(tblRecord[intNext+1])
if strTag == "ROLE" and strRecId == strWitId then
strType = strType.." and "..strVal -- Current record Individual is a Witness so update fact Type with Role
end
if isOptMin then -- 'Move to Fact Note Min' rule so delete _SHA[NR] subsidiary lines -- V5.6
doRemoveAll("2",intNext)
doDelete(intNext)
intNext = intNext - 1
end
end
if intLev <= 1 then break end -- Quit at end of fact
intNext = intNext + 1
end
tblRecord[intType] = strType:gsub("Role Unknown and ","") -- Save updated fact type -- V5.6
break
end
end
break
end
end
end
if strWitId then -- Lookup Witness Individual record name
strName = strRecordText(strWitId) -- Return [RecId] Name or TNG hyperlink -- V5.6 -- V2.2
else
strName = "Name:\t "..strWitness -- Name Only Witness name
end
local arrNote = {} -- Move all the Fact Witness details to local Note -- V5.6
local arrSour = {}
local strRole = "Unknown:\t "
local intNext = intLine+1
while intNext <= #tblRecord do -- Search subsequent lines for Witness/Role/Source/Note
local strNext = tblRecord[intNext]
local intLev, strTag, strVal = getParts(strNext)
if intLev <= 2 and not strTag:match("SHA[RN]") then break end -- Quit when all Fact Witness tags done -- V5.6
if strTag == "SOUR" or intLev > 3 then -- Save all Source Citation lines -- V1.9
if strGedExport == "5.5.1" then
tblRecord[intNext] = strNext:gsub("^(%d)",tostring(intLev-1)) -- Move Source Citation up to Fact -- V5.6
intNext = intNext + 1
else
strNext = doPrune(strNext) -- Handle any Rich Text format Notes -- V6.3
table.insert(arrSour,"\n"..strNext)
doDelete(intNext) -- Delete the Source Citation line -- V1.8
end
elseif strTag:match("_SHA[RN]") then -- Handle allsubsequent Fact Witnesses -- V5.6
if strRole then
table.insert(arrNote,strRole..strName) -- Move previous Witness Role to Fact Note
end
local strWitId = strVal:match("@(I%d+)@") -- Lookup Witness Individual record name
if strWitId then
strName = strRecordText(strWitId) -- Return [RecId] Name or TNG hyperlink -- V5.6
else
strName = "Name:\t "..strVal -- Name Only Witness name
end
strRole = "Unknown:\t "
doDelete(intNext) -- Delete Fact Witness line
elseif strTag == "ROLE" then -- Save the Role
strRole = strVal..":\t "
if strVal == "~Principal~" then
strRole = dicLabel["Principal"] -- Translate 'Principal' label -- V5.6
if #strRole > 0 then
strRole = strRole..":\t "
end
end
table.insert(arrNote,strRole..strName) -- Move the Witness Role & Name to Fact Note
strRole = nil
doDelete(intNext) -- Delete Role line -- V1.8
elseif strTag == "NOTE" then
strVal = doPrune(strVal) -- Handle any Rich Text format Notes -- V6.3 -- V5.2
table.insert(arrNote,strVal) -- Move Witness Note to Fact Note
doDelete(intNext) -- Delete Note line -- V1.8
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
table.insert(arrNote,strRole..strName) -- Move last Witness Role to Fact Note
end
if #arrSour > 0 then
arrNote[#arrNote] = arrNote[#arrNote]..table.concat(arrSour) -- Append Source Citations to Fact Note
end
if dicWhere["Witness Role"] == StrBefore then
for intNote = #arrNote, 1, -1 do -- Reverse Fact Witness Note entries -- V5.6
doFactNote(arrNote[intNote])
end
else
for intNote = 1, #arrNote do -- Forward Fact Witness Note entries -- V5.6
doFactNote(arrNote[intNote])
end
end
return "" -- Remove original 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 doAddrTidy(strLev,strAddr) -- Tidy Address field -- V2.3
-- strLev ~ Level of ADDR line
-- strAddr ~ Address of ADDR line
return strLev.." ADDR "..strTidyText(strAddr) -- V5.0
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[StrName] -- V3.3 -- V5.0 StrName instead of StrAbbr
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
strMsid = tblGedSelect[strGedImport].Msid -- Media sequence id tag for FH V6 = _ASID and for FH V7 = _SEQ -- V4.0
strFact = tblGedSelect[strGedExport].Fact -- Custom fact tag for GEDCOM 5.5 = 1 EVEN and GEDCOM 5.5.1 = 1 FACT -- V4.0
if StrName == "MYH" then strFact = "1 EVEN" end -- MYH uses 5.4.1 but EVEN tag -- V6.0
return "2 VERS "..strGedExport -- Output Gedcom version
end
return nil -- Retain original line
end -- local function doVersion
function CopyBranch(ptrSource,ptrTarget) -- Copy subsidiary branch of record -- V5.5
local strTag = fhGetTag(ptrSource)
if strTag == "_FMT" then return end -- Skip rich text format code
if strTag == "_FIELD" then -- Substitute metafield shortcut
strTag = fhGetMetafieldShortcut(ptrSource)
end
local ptrNew = fhCreateItem(strTag,ptrTarget,true)
if ptrNew:IsNull() then return end -- Escape if item not created
if strTag == "_SRCT" then
fhSetValueAsLink(ptrNew,dicSour.SRCT) -- Special case to prevent Source Template Update date/time changing
else
fhSetValue_Copy(ptrNew,ptrSource)
end
CopyChildren(ptrSource,ptrNew)
end -- function CopyBranch
function CopyChildren(ptrSource,ptrTarget) -- Copy child branches of record -- V5.5
local ptrFrom = ptrSource:Clone()
ptrFrom:MoveToFirstChildItem(ptrFrom)
while ptrFrom:IsNotNull() do
CopyBranch(ptrFrom,ptrTarget)
ptrFrom:MoveNext()
end
end -- function CopyChildren
function DeleteChildren(ptrSource) -- Delete child branches of record -- V5.5
local ptrFrom = ptrSource:Clone()
ptrFrom:MoveToFirstChildItem(ptrFrom)
while ptrFrom:IsNotNull() do
local ptrItem = ptrFrom:Clone()
ptrFrom:MoveNext()
fhDeleteItem(ptrItem)
end
end -- function DeleteChildren
local function doReform(strTag,strLab) -- Convert a Format Template to formatted text -- V5.5
-- strTag ~ Source Template Format tag ~.FOOT, ~.SHRT, ~.BIBL
-- strLab ~ Note text label
local strForm = fhGetValueAsText(fhGetItemPtr(dicSour.SRCT,strTag)) -- Format Template
strForm = strForm:gsub("{([_%w]-):BOOK}", StrBookStyle) -- Replace 'Book' & 'Title' styles with style codes -- V5.3
strForm = strForm:gsub("{([_%w]-):TITLE}",StrTitlStyle)
strForm = doPrune("0 X "..strForm.."\n_FMT"):gsub("^0 X ","") -- Honour rich text option
strForm = strForm:gsub("<(/?[ibcu])>",StrHideStyle) -- Hide style codes temporarily as Title Format disallows them -- V5.3
fhSetValueAsText(fhGetItemPtr(dicSour.SRCT,"~.TITL"),strForm)
fhSrcEnableAutoTitle(dicSour.SOUR,true) -- Obtain the formatted text in Title except for Citation-specfic Metafields
strForm = fhGetValueAsText(fhGetItemPtr(dicSour.SOUR,"~.TITL"))
strForm = strForm:gsub(StrMatchPref,"<%1>") -- Reveal style codes -- V5.3
strForm = strForm:gsub(StrMatchSuff,"<%1>")
strForm = strForm:gsub("(.-) ",string.upper) -- Convert capitals -- V5.3
if #dicLabel[strLab] > 0 then
tblRecord.Arg = strLab
addNoteText(strForm,"1 NOTE","1 CHAN") -- Insert formatted text into 1 CONT Record Note but only if Note text Label is defined -- V5.7
end
return strForm
end -- local function doReform
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
local strFoot = ""
local intLine = tblRecord.Line + 1
if strRef then
if not dicSour.SOUR then -- Create dummy Source record and Source Template record -- V5.5
dicSour.SOUR = fhCreateItem("SOUR")
dicSour.SRCT = fhCreateItem("_SRCT") -- These increase Record Id unless Edit > Undo Plugin Updates is used!
fhUpdateDisplay()
end
local strRid = tblRecord[1]:match("0 @S(%d+)@ SOUR") -- Get pointer to actual Project Source record
local ptrSour = fhNewItemPtr()
ptrSour:MoveToRecordById("SOUR",tonumber(strRid))
local ptrSrct = fhGetItemPtr(ptrSour,"~._SRCT>") -- Get pointer to actual Project Source Template
DeleteChildren(dicSour.SRCT)
DeleteChildren(dicSour.SOUR)
CopyChildren(ptrSrct,dicSour.SRCT) -- Copy actual records to dummy records
CopyChildren(ptrSour,dicSour.SOUR)
fhUpdateDisplay()
strFoot = doReform("~.FOOT","Footnote") -- Convert Footnote Format to formatted text Note
doReform("~.SHRT","Short Footnote") -- Convert Short Footnote Format to formatted text Note
doReform("~.BIBL","Bibliography") -- Convert Bibliography Format to formatted text Note
StrReformat = "Use Edit > Undo Plugin Updates (Ctrl+Z) to avoid increased Source Record Id." -- V5.5
end
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 ) -- V5.5
doDelete(intLine)
elseif intLev == 2 then -- Convert template Field value line
if strTag == "REPO" then
doInsert("1 REPO "..strVal,intLine) -- Use generic Repository link even with Remove entirely
intLine = intLine + 1
strVal = strRecordName(strVal:match("@(R%d+)@")) -- Translate link to Repository name for add to Note -- V4.5
elseif strTag:match("^_") then
break
end
if strRef then -- Convert template field to Note -- V5.2
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
else
doRemoveAll("1",intLine-1) -- Remove template lines entirely -- V5.2
end
elseif strRef == "Reformat" then
if intLev == 1 then
if strTag == "TITL" then -- Reformat TITL and ABBR fields
if StrName ~= "GST" -- V5.7
and StrName ~= "RMT"
and StrName ~= "TNG" then -- V5.5
strFoot = strFoot:gsub("<(/?[ibu])>","") -- Unless GST/RMT/TNG only support Capitals & Single/Double Quotes -- V5.3
end
doModify("1 TITL "..strFoot,intLine) -- Modify the 1 TITL to hold Footnote text
intLine = intLine + 1
local intLev, strTag, strOld = getParts(tblRecord[intLine])
if intLev == 1 and strTag == "ABBR" then -- Move Title value to Short Title
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
if strRef and #dicLabel["Source Template"] > 0 then -- Convert Source Template link to a Note Record link -- V5.2 -- V5.7
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
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("^Convert to Notes")
or strRule:match("^Footnote to Title") then -- Convert Metafield into Citation local Note -- V5.2 -- V5.7
-- Use Footnote in "Footnote to Title" mode if it includes a Citation _FIELD and add new option to omit _FIELD values e.g. "Footnote Only" ???
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") or #dicLabel["Source Template"] == 0 then
return doRemoveAll("0") -- Remove record completely -- V5.7
end
if strRule:match("^Convert to Notes")
or strRule:match("^Footnote to Title") then -- V5.2 -- V5.7
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 %a+") then -- V6.0
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
local strTitl = strRule:match("^Move to (.+)") -- V6.0 for GKP
local strDate = ""
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)
strTitl = strLine:match("Title: ([^\r]*)") or strTitl -- V6.0 for GKP
strDate = strLine:match("Date: ([^\r]*)") or strDate
end
if strRule:match("^Move to Task Record") then -- V6.0 for GKP
local strTask = strNote:gsub("N","TK")
doInsert("0 "..strTask.." _TASK") -- Create synthetic Task record
doInsert("1 NOTE "..strNote)
doInsert("1 _STARTDATE "..strDate)
doInsert("1 _GOAL General Research")
end
if strRule:match("^Move to Researches") then -- V6.0 for GKP
local strRsch = strNote:gsub("N","RS")
doInsert("0 "..strRsch.." _RESEARCH") -- Create synthetic Research record
doInsert("1 NOTE "..strNote)
doInsert("1 NAME "..strTitl)
doInsert("1 _STARTDATE "..strDate)
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 doLocation(strRid,strPlac) -- GKP Convert Place Record to Location Record -- V6.0
-- strRid ~ Whole record id
-- strPlac ~ Place tag & name
strRid = strRid:gsub("P","L")
strPlac = strPlac:gsub("_PLAC ","")
for intLine = 2, #tblRecord do -- Search through record
local intLev, strTag, strVal = getParts(tblRecord[intLine])
if strTag == "STAN" then -- STANdardized to Note
doRecNote(doNameTag(strTag).Name:gsub("^#%)","")..strVal,intLine)
break
end
end
doInsert("1 NAME "..strPlac,2) -- Add place name
return "0 "..strRid.."_LOC"
end -- local function doLocation
local function doBlankFact(strTag,strVal) -- Blank Fact needs a local Note for Ancestry -- V5.9
-- strTag ~ Tag for current line
-- strVal ~ Value of current line
if isRecord("INDI, FAM") then
local dicTag = dicNameTag[strTag] or {}
strVal = strVal or ""
if #strVal > 1 then -- Attribute has a value
for intLine = tblRecord.Line + 1, #tblRecord do
local intL, strT, strV = getParts(tblRecord[intLine])
if intL == 1 then
break
elseif intL == 2 and strT == "NOTE" then
tblRecord[intLine] = "2 NOTE \r"..strV
break
end
end
elseif dicTag.E or dicTag.A or dicTag.L then -- Event or Attribute or LDS Ordinance with no Attribute value
local intLine = tblRecord.Line + 1
local intL, strT, strV = getParts(tblRecord[intLine])
if intL == 2 and strT == "TYPE" then -- Skip custom Fact Type
intLine = intLine + 1
intL, strT, strV = getParts(tblRecord[intLine])
end
if intL == 1 or ( intL == 2 and strT == "SOUR" ) then -- No level 2 Date, Place, Note, etc, except Source
doInsert("2 NOTE No recorded Date/Place/etc.", intLine) -- Insert local Note so Ancestry does not exclude the Fact
end
end
end
return nil -- Retain original line
end -- local function doBlankFact
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
if dicRule["1 AZ"] then doBlankFact(strTag,strVal) end -- Ancestry any blank Fact check -- V5.9
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
if dicRule["1 AZ"] then doBlankFact("FACT",strVal) end -- Ancestry any blank Fact check -- V5.9
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 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 function doNote2Fact(strVal) -- Convert Individual (ANC) or Family Record Note to synthetic Fact Note -- V2.2 -- V3.3 -- V5.9
-- strVal ~ Value of NOTE line
local strArg = tblRecord.Arg or "Record Note" -- V5.9
if isRecord("INDI") and strArg == "Record Note"
or 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 "..strPrefix..strArg,intLine) -- V3.3 -- V5.9
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 StrNode ~= "GST" then -- V5.0 -- StrNode instead of StrMode
return doNoteLink("1",strVal)
end
return nil -- Retain original line
end -- local function doNote2Fact
local dicCiteNote = {FILE=" Object";FORM=" Object";TITL=" Object";OBJE=" Object";FAMC=" Parent";FAMS=" Spouse";}
local function doCiteNote(strLev,strVal) -- Convert Individual/Family/Fact long 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
if #strVal < 256 then
if intLev == 1 then return doNote2Fact(strVal) else return nil end -- V5.9
end
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
doInsert("2 NOTE Record Note", intLine) -- V5.9
intLine = intLine + 1
end
if intLev == 2 and strTag == "TYPE" then
doInsert("2 NOTE Fact Note", intLine) -- V5.9
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 isCitation(intLine,intLev) -- Check if part of a citation -- V5.3
for intPrev = intLine-1, 2, -1 do
local intPre, strTag = getParts(tblRecord[intPrev])
if intPre == intLev-1 then -- Find parent SOUR citation
return ( strTag == "SOUR" )
end
end
return false
end -- local function isCitation
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%-%._~:/%?#@!%$&\'\\%(%)%*%+,;=%%]+)')
local strUrl = 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
if isCitation(intLine,intLev) then
doInsert(intLev.." _LINK "..strUrl,intLine) -- Found parent SOUR citation so add _LINK tag with URL -- V4.6 -- V5.3
end
end
return nil -- Retain original line
end -- local function doWeblink
local function doWebTags(strLev,strVal) -- For source/citation local Note with Weblink URL add _WEBTAG URL for RMT -- V5.3
-- strLev ~ Level of current line
-- strVal ~ Value of current line
local intLev = tonumber(strLev)
local intLine = tblRecord.Line
if isRecord("SOUR")
or ( intLev > 1 and isCitation(intLine,intLev) ) then -- Source/citation so add _WEBTAG tag with URL -- V5.3
for strUrl in strVal:gmatch('([hf]t?tps?://[A-Za-z0-9%-%._~:/%?#@!%$&\'\\%(%)%*%+,;=%%]+)') do
doInsert(intLev.." _WEBTAG",intLine)
local intLev = intLev + 1
intLine = intLine + 1
doInsert(intLev.." NAME WebTag",intLine)
intLine = intLine + 1
doInsert(intLev.." URL "..strUrl,intLine)
intLine = intLine + 1
end
end
return nil -- Retain original line
end -- local function doWebTags
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("^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
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
elseif strForm == "Quarters" then -- Convert BET/AND quarter months to Q1-4 yyyy -- V5.1
local strYear1, strYear2 = strText:match("^2 DATE BET %u%u%u (%d%d%d%d) AND %u%u%u (%d%d%d%d)$")
if strYear1 and strYear2 and strYear1 == strYear2 then
strText = strText:gsub("BET JAN (%d+) AND MAR (%d+)","Q1 %1")
strText = strText:gsub("BET APR (%d+) AND JUN (%d+)","Q2 %1")
strText = strText:gsub("BET JUL (%d+) AND SEP (%d+)","Q3 %1")
strText = strText:gsub("BET OCT (%d+) AND DEC (%d+)","Q4 %1")
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","MB_OK","MB_ICONEXCLAMATION")
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 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
local _, strFile = general.SplitFilename(putFile)
local strMsg = strErr..strSize.."\n\nMultimedia Record Id: OBJE ["..strRid.."]"..strSid..strArea.."\n\nSource: "..getFile.."\n\nExport: "..strFile..strCopy
strErr = strErr:sub(1,15) -- Use first 15 chars to inhibit similar messages -- V5.9
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 -- crashed 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,getAnsi,getWasAnsi,putFile,putAnsi,putWasAnsi,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()
-- getAnsi ~ Ansi path of source media file
-- getWasAnsi~ true if getFile was Ansi compatible
-- putFile ~ Full path of target media file
-- putAnsi ~ Ansi path of target media file
-- putWasAnsi~ true if putFile was Ansi compatible
-- 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 oldType,oldT,oldL,oldB,oldR = putType,intT,intL,intB,intR -- Keep original parameters in case of error -- V3.7
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(getAnsi) -- Load source image from file and check for errors -- V5.0 getFile > getAnsi
if not getWasAnsi then general.DeleteFile(getAnsi) end -- V5.0
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(putAnsi,outType,putImage) -- Save image file with format type matching file type -- V3.7 -- V5.0 putFile > putAnsi
if not putWasAnsi then general.MoveFile(putAnsi,putFile) end -- V5.0
else
intErr = im.ERR_DATA -- GetOpenGLData failure detected -- V4.0
end
getImage:Destroy()
putImage:Destroy() -- Release image memory
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,getAnsi,getWasAnsi,putFile,putAnsi,putWasAnsi,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,strFile) -- Update the Result Set arrays -- V3.7 -- V4.5 -- V5.0
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
-- 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 -- V6.3 [\\/] to cater for \ & / delimiter
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
putFile = putPath..putFile -- Allow Part Frame Folders -- V4.0
end
if TblOption.Seps == "ON" then
putFile = putFile:gsub("\\","/") -- Conditional MacOS/Unix style / path separators -- V6.0
end
strFormat = nil
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"
strFormat = "jpeg" -- V5.3
end
end
if general.FlgFileExists(getFile) then -- Source media file exists
local isOK,getAnsi,getWasAnsi = pcall(general.FileNameToANSI,getFile,"Export Gedcom File Source.jpg") -- V5.2
if not isOK then -- Report string conversion failed -- V5.2
local strErr = getAnsi:gsub('%[string ".-"%]:%d+: ','') -- [string "C:\ProgramData\Calico Pie\Family Historian\Pl..."]:6832: fhConvertUTF8toANSI - String conversion failed
dicErr[strErr] = (dicErr[strErr] or 0) + 1
if dicErr[strErr] <= 99 then
setResultSet(0,-4,strErr,getFile) -- Update the Result Set for fhConvertUTF8toANSI - String conversion failed
end
return putFile
end
if not getWasAnsi then doCopyFile(getFile,getAnsi) end -- V5.0 -- V5.2
local putAnsi,putWasAnsi = general.FileNameToANSI(putFile,"Export Gedcom File Target."..putType) -- V5.0
if not putWasAnsi and general.FlgFileExists(putFile) then doCopyFile(putFile,putAnsi) end -- V5.0 -- V5.2
if not general.FlgFileExists(putFile) -- No target file, or modification date-times differ
or lfs.attributes(getAnsi,"modification") > lfs.attributes(putAnsi,"modification") then -- V5.0 get/putFile > get/putAnsi
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
general.MakeFolder(general.GetParentFolder(putFile)) -- V5.0
putFile = doProcessImage(getFile,getAnsi,getWasAnsi,putFile,putAnsi,putWasAnsi,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
if not putWasAnsi then general.DeleteFile(putAnsi) end -- V5.0
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
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
local intLine = 1
while intLine < #arrRecord do -- V6.2
intLine = intLine + 1
local strLine = doPrune(arrRecord[intLine]) -- V1.9
local intLev, strTag, strVal = getParts(strLine) -- V2.0 intLev & getParts
if strTag:match("_NOT[AE]") then
local is_Note = dicRule[" _NOTA"] or dicRule[" _NOTE"] or {} -- Remove annotation notes? -- V6.2
if is_Note.Item:match("^Remove entirely") then
intLine = doDelete(intLine)
strLine = doPrune(arrRecord[intLine]) -- V1.9 Move on to next line
intLev, strTag, strVal = getParts(strLine) -- V2.0 intLev & getParts
end
end
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 StrName:match("GST") then -- V5.0 StrName instead of StrAbbr
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 = strFormat or strVal -- Use adjusted file format where necessary -- V5.3
strVal = strVal:gsub("^.+$",tblGedSelect[strGedExport].Type)
isNew = isNew or ( strOld ~= strVal )
strFormat = nil
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"
or strLine == "3 FILE " then -- V5.0
return doRemoveAll(strLev) -- Remove LMO Sort Date or missing File Link media -- V3.5
end
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 StrName:match("FH[56]") then -- V5.0 StrName instead of StrAbbr
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 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
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)
local isCapt = false -- Caption Note? -- V6.2
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
isCapt = true -- V6.2
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
if isCapt then -- Special case of Caption Note -- V6.2
tblRecord.Arg = "Caption Note"
doLocNote(tostring(intLev),strVal) -- Insert LMO Caption Note -- V6.2
tblRecord.Arg = dicArg
else
doInsert(strNew,intLine) -- Add new Local Media Object tag line
intLine = intLine + 1
end
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" or strMode == "CROP" then -- All Local Media Objects and ABS Media Links -- V3.2 -- V6.2
tblRecord[strPref] = true -- GKP & TNG add _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
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("{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
if strMode == "CROP" and not isNotFull then -- V6.2
local intLev = tonumber(strLev)
if tblObje.ASID._AREA then -- TNG adds CROP, TOP, LEFT, HEIGHT, WIDTH tags for _AREA framed full image -- V6.2
local strArea = tblObje.ASID._AREA[1].Val or "{}"
local tblArea = strArea:match("{(.*)}"):splitnumbers() -- Image frame _AREA co-ordinates {Top,Left,Bottom,Right} -- V1.4
local intT = tblArea[1] -- Top
local intL = tblArea[2] -- Left
local intH = tblArea[3] - intT -- Bottom - Top => Height
local intW = tblArea[4] - intL -- Right - Left => Width
local strLev = tostring(intLev+1)
doInsert(strLev.." CROP",intLnk)
intLine = intLine + 1
strLev = tostring(intLev+2)
doInsert(strLev.." TOP "..intT,intLnk+1)
intLine = intLine + 1
doInsert(strLev.." LEFT "..intL,intLnk+2)
intLine = intLine + 1
doInsert(strLev.." HEIGHT "..intH,intLnk+3)
intLine = intLine + 1
doInsert(strLev.." WIDTH "..intW,intLnk+4)
intLine = intLine + 1
end
if tblObje.ASID.NOTE and not isLocObje then
local strNote = tblObje.ASID.NOTE[1].Val or "" -- TNG adds local caption Note to framed full image -- V6.2
local dicArg = tblRecord.Arg
local strArg = dicArg.Lab
local strLab = dicLabel[strArg] or strArg..":\t" -- Label for Caption Note -- V3.3
local strLev = tostring(intLev+1)
doInsert(strLev.." NOTE "..strLab..strNote,intLnk)
intLine = intLine + 1
tblObje.ASID.NOTE = nil
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 isCROP = not isLocObje and tblObje and (tblObje.ASID._AREA or tblObje.ASID.NOTE)
local isUseFull = not( strMode == "CROP" and isCROP ) -- Use full frame unless CROP of AREA/NOTE needed -- V3.3 -- V6.2
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
setObjectUsed(strRiD) -- Use the Sid=0 & Rid key to set Media Object details as Used -- V6.2
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 = strFile -- V5.0
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()) 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("^[Mm]edia[\\/]","Media\\") -- Ensure Media folder name capitalised -- V4.0 -- V6.3 [\\/] to cater for \ & / delimiter
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 -- V6.3 [\\/] to cater for \ & / delimiter
-- Convert .tif into .jpg for GedSite? Not needed, but it was contemplated to go here. --
else
local getType = "~"
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
if TblOption.Seps == "ON" then
getFile = getFile:gsub("\\","/") -- Conditional MacOS/Unix style / path separators -- V6.0
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 ""
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
strKeys = strKeys:gsub("^[, ]+",""):gsub("[, ]+$",""):gsub("[, ]+",", ") -- Remove redundant commas -- V5.2
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 _, 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 doMakeEvent(strInit,strRid,arrArg) -- Make Birth/Death from 1st dated fact with matching Timeframe -- V5.0
-- strInit ~ Record Id initial letter ~ Not used
-- strRid ~ Record Id numerical digits ~ Not used
-- arrArg ~ List of Timeframes SOON-AFTER-BIRTH &/or POST-DEATH
local intMake = nil -- Line at which to make Birth/Death event
local strMade = nil -- Names of BIRT/DEAT events made
for _, strTime in ipairs (arrArg) do -- Loop through Timeframes
local strFact = strTime:match("%-(%u%u%uT)H$") -- Extract event tag BIRT/DEAT
local intLine = ( intMake or tblRecord.Line ) + 1 -- Line to resume or start search from
local strPref = nil -- Prefix EVEN- or _ATTR- for custom fact
local strYear = nil -- Date year from matching Timeframe fact
local strPart = nil -- Place part from matching Timeframe fact
intMake = nil -- Line at which to make Birth/Death event
repeat
local intLev, strTag, strVal = getParts(tblRecord[intLine]) -- Search record for facts with matching Timeframe
if intLev == 1 then
strPref = nil
if arrMake[strTag] == strTime then -- Standard fact matches Timeframe
intMake = intMake or intLine -- Note line to make new Birth/Death event
elseif strTag == "EVEN" then
strPref = "EVEN-" -- Cater for custom events
elseif strTag == "_ATTR" or strTag == "FACT" then
strPref = "_ATTR-" -- Cater for custom attributes
end
elseif intLev == 2 then
if strTag == "TYPE" and strPref then
if arrMake[strPref..strTag] == strTime then -- Custom fact matches Timeframe
intMake = intMake or intLine -- Note line to make new Birth/Death event
end
elseif strTag == "DATE" and intMake then
strYear = strVal:match("%d%d%d%d") -- Extract year from Date
local intLev, strTag, strVal = getParts(tblRecord[intLine+1])
if intLev == 2 and strTag == "PLAC" then
strPart = strVal:match(", *([^,]-)$") -- Extract last part from Place
end
end
strPref = nil
end
intLine = intLine + 1
until ( intMake and strYear ) or intLine > #tblRecord
if intMake and strYear then -- Make Birth/Death event with year as Date and possibly last Place part
doInsert("1 "..strFact,intMake)
intMake = intMake + 1
doInsert("2 DATE ABT "..strYear,intMake)
if strPart then
intMake = intMake + 1
doInsert("2 PLAC "..strPart,intMake)
end
intMake = intMake + 2 -- Skip fact that instigated this event
strMade = strMade or " DNA"
strMade = strMade.." "..strFact
end
end
return strMade -- Return name of synthetic BIRT/DEAT event(s) created if any
end -- local function doMakeEvent
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.."@"
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
else
fhMessageBox("\n Record mismatches generic rule. \n","MB_OK","MB_ICONEXCLAMATION") -- 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,anyArg) -- 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
-- anyArg ~ Number or text 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],anyArg) -- Add the Note/Source/Fact Id or Timeframe 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 for Tidy Place Record Name Conflicts
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 &/or synthesise DNA Birth/Death Events -- V2.0
-- tblRecord ~ Array of text lines in GEDCOM record
-- arrLineNo ~ Array of their import line numbers
-- strRid ~ Record Id of Principal Individual/Family
local strSHAR = "_SHAR" -- V5.6
local dicSHAR = dicRule["2 _SHAR"]
if IntFhVersion == 5 or not dicSHAR.Arg then -- Except if the Witnessed Facts are being kept or removed -- V3.8
if not isMake then return end -- Synthesise DNA Birth/Death Events? -- V5.6
strSHAR = " " -- i.e. Cater for any combination of Witnessed Facts and DNA Birth/Death Events
end
local is_SHAR = false
local isBIRT = false
local isDEAT = false
local arrLine = {}
local arrNumb = {}
local dicIndi = {} -- List of Witness Individual Records -- V5.6
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 = {}
dicIndi = {} -- V5.6
dicIndi["@"..strRid.."@"] = true
if strTag == "BIRT" then isBIRT = true end
if strTag == "DEAT" then isDEAT = true end
elseif strTag == strSHAR then -- Shared/Witnessed Fact found -- V5.6
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 -- V6.4
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 Unknown" -- 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 Unknown") -- V3.3
arrLine[2] = strType
else -- Else add TYPE for standard Event/Attribute
local strType = dicTag.Name:gsub("^.- (.+):%s+$","2 TYPE "..strPrefix.."%1 Role Unknown") -- V3.3
table.insert(arrLine,2,strType)
table.insert(arrNumb,2,arrNumb[1])
end
table.insert(arrLine,"2 _SHAR @"..strRid.."@") -- Synthesise unique ~Principal~ Role -- V5.6
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
if not dicIndi[strVal] then
local strRule, strType = getElements(strVal:gsub("@",""))
addLinkRecRule(strRule,strType,doShareFact,#arrFact) -- Create record tag Rule for new Witness Individual Record -- V5.6
dicIndi[strVal] = true
end
end
if not (strLine:match("^2 HUSB") or strLine:match("^2 WIFE") or strLine:match("^%d AGE")) then -- V3.3 -- V5.6
table.insert(arrLine,strLine) -- Save the Fact Lines and Line Nos except Family fact Ages
table.insert(arrNumb,arrLineNo[intLine])
end
end
if isMake then -- Are synthetic DNA Birth/Death Events needed? -- V5.0
local strRule, strType = getElements(strRid)
if not isBIRT then -- Yes, make Birth from 1st dated fact with Timeframe=Soon After Birth -- V5.0
addLinkRecRule(strRule,strType,doMakeEvent,"SOON-AFTER-BIRTH")
end
if not isDEAT then -- Yes, make Death from 1st dated fact with Timeframe=Post-Death -- V5.0
addLinkRecRule(strRule,strType,doMakeEvent,"POST-DEATH")
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 dicRule[" CENS"] then
doConJoin(tblRecord,arrLineNo)
for intLine = 2, #tblRecord do -- If Census 'Copy TfS to Note' rule exists then search Source record -- V5.3
local strLine = tblRecord[intLine]
if strLine:match("^1 TEXT") then
if not dicSour[strRid] then dicSour[strRid] = {} end
dicSour[strRid].TfS = strLine -- Save the Text from Source tag line -- V5.3
break
end
end
end
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 -- V6.0
if 1 == iup_gui.WarnDialogue(" Tidy Place Record Name Conflicts ","\n See the 'Extra Options' tab, 'Tidy Places && Addresses' option. \n\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 the Place Record Name Conflicts \n so they can be merged or edited but no GEDCOM file will be exported. \n\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 doUniqueId(strUID) -- Handle Fact Definitions after Header or format Unique Id _UID elsewhere (was doFactDefs) -- V5.0
if isRecord("INDI") then -- Format Unique Identity -- V5.0
local strHex = strUID:gsub("-",""):upper() -- Determine whether passed UID is valid (keep same value if not)
if tonumber(strHex,16) and (#strHex == 32 or #strHex == 36) then-- Hexadecimal and valid length
local strHex32 = strHex:sub(1,32)
local intAlpha = 0
local intBravo = 0
for strByte in strHex32:gmatch("%x%x") do -- Calculate checksum using published method
local intValue = tonumber(strByte,16)
intAlpha = intAlpha + intValue
intBravo = intBravo + intAlpha
end
local strCheck = string.format('%x',intAlpha):sub(-2) .. string.format('%x',intBravo):sub(-2)
local strHex36 = strHex32 .. strCheck:upper()
if #strHex == 32 or strHex36 == strHex then -- Valid original checksum
if StrName == "MFT" then -- My Family Tree prefers hypenated GEDCOM 7.0 format with UID tag
return "1 UID "..strHex:sub(1,8)..'-'..strHex:sub(9,12)..'-'..strHex:sub(13,16)..'-'..strHex:sub(17,20)..'-'..strHex:sub(21,32)
elseif StrName == "MYH" then -- My Heritage prefers unhyphenated without checksum
return "1 _UID "..strHex32
else -- Legacy, RootsMagic, et al, prefer unhyphenated with checksum
return "1 _UID "..strHex36
end
end
end
return nil -- Retain original line with invalid UID
end
-- Add Fact Definitions after Header or find Facts to make Birth/Death events for DNA -- V5.0
if not(isRecord("HEAD") and (isFact or isMake)) then return "" end -- If not HEAD record or disabled then just remove original line -- V3.3 -- V5.0
local tblFact = {} -- Fact definitions table
local dicLine = { Name="1 _NAME "; Label="1 _LABEL "; Abbr="1 _ABBR "; Template="1 _SENT "; Role="1 _ROLE "; Verb="2 _VERB "; Sentence="2 _SENT "; }
arrMake = {} -- Facts with Timeframe=SOON-AFTER-BIRTH or Timeframe=POST-DEATH -- V5.0
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
local strName = "" -- Name of tabulated Timeframe facts -- V5.0
local strFactsAnsi, wasAnsi = general.FileNameToANSI(strFactsFile,"Export Gedcom File Facts.fhf") -- V5.0
if not wasAnsi then doCopyFile(strFactsFile,strFactsAnsi) end -- V5.0 -- V5.2
for strLine in encoder.FileLines(strFactsAnsi,"UTF-8") do -- Read each line from Fact Set file -- V5.0 -- 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("^([_ATREVN]+)%-.-%-([IF][AE])$","%1-%2") -- 28 Nov 2016 for 5.5 to match Root array below < this is current
strName = strFact:gsub("%-[IF][EA]$","") -- Name of fact for isMake Timeframe -- V5.0
end
else
if isFact then -- Tabulate fact definition tag lines -- V5.0
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
if isMake then -- Tabulate any Facts with required Timeframe -- V5.0
if strLine:match("^Name=") then
if strName:match("^EVEN")
or strName:match("^_ATTR") then -- Adjust name of custom facts to match GEDCOM -- V5.0
strName = strName:match("^([_%u]+%-).+")..strLine:match("^Name=(.+)")
end
elseif strLine == "Timeframe=SOON-AFTER-BIRTH" or strLine == "Timeframe=POST-DEATH" then
arrMake[strName] = strLine:match("^T%l+=([%u%-]+)") -- Tabulate each fact name with its Timeframe -- V5.0
end
end
end
end
if not wasAnsi then general.DeleteFile(strFactsAnsi) end -- V5.0
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
local strGroupAnsi, wasAnsi = general.FileNameToANSI(strGroupIndex,"Export Gedcom File GroupIndex.fhdata") -- V5.0
if not wasAnsi then doCopyFile(strGroupIndex,strGroupAnsi) end -- V5.0 -- V5.2
for strLine in encoder.FileLines(strGroupAnsi,"UTF-8") do -- Read each line in Project only GroupIndex -- V5.0 -- V4.2 -- V5.9
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
if not wasAnsi then general.DeleteFile(strGroupAnsi) end -- V5.0
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,"UTF-8") do -- Read each line in ProgramData GroupIndex -- V3.6 -- V5.9
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
local strLanguage = iup_gui.GetRegKey("HKCU\\SOFTWARE\\Calico Pie\\Family Historian\\2.0\\Preferences\\Output Language Id")
if strLanguage and #strLanguage ~= 0 then -- Import Language Fact Set file last -- V5.4
doLoadFactsTable(fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Lang\\"..strLanguage.."\\lang_facts.fhf")
end
if isFact then
for intFact, arrFact in ipairs (tblFact) do
for intFact, strLine in ipairs (arrFact) do -- Export Fact Tags from Fact table
doInsert(strLine)
end
end
end
return "" -- Delete original line
end -- local function doUniqueId
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 doBapm2Chr() -- Convert BAPtisM to CHRistening when no BIRTh for TNG -- V5.1
for intLine = 2, tblRecord.Line-1 do
if tblRecord[intLine]:match("^1 BIRT") then return nil end -- Retain original line
end
return "1 CHR"
end -- local function doBapm2Chr
local function doTfS2Note() -- Copy 1st source citation Text from Source to CENSus local Note -- V5.3
if isRecord("INDI") then
local strText = nil
local intNote = nil
local strNote = ""
local intLine = tblRecord.Line
for intLine = intLine+1, #tblRecord do
local intLev, strTag, strVal = getParts(tblRecord[intLine])
if intLev == 2 then
if strTag == "NOTE" then -- Find existing Note
intNote = intLine
strNote = strVal.."\r"
elseif strTag == "SOUR" and not strText then -- Find 1st Source Text from Source
local strRid = strVal:match("^@(S%d+)@")
if strRid and dicSour[strRid] then
strText = dicSour[strRid].TfS
end
end
elseif intLev == 1 then -- Find end of Census event
if strText then
local _, _, strVal = getParts(doPrune(strText)) -- Copy Text from Source to Note
if intNote then
doModify(strNote..strVal,intNote)
else
doInsert("2 NOTE "..strVal,intLine)
end
xxx=0
end
break
end
end
end
return nil -- Retain original line
end -- local function doTfS2Note
local function doCens2Resi() -- Convert Individual CENSus to RESIdence for Ancestry Census hints -- V3.4
local dicRule = dicRule[" CENS"] or {}
local strRule = dicRule.Item or " "
if strRule:match("^Copy T f S to Note") then doTfS2Note() end -- Are both CENSus rules enabled? -- V5.3
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("%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 GKP GSP HER MFT MYH 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 -- V6.0 GKP MYH
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" ;"Handle Unique Id" ; "p" ;"^1 _UID (.*)" ;doUniqueId ;nil ;"Std fh5 fh6" }) -- V3.3 All can export Fact Definitions if option enabled, except fh5 -- V4.0 and fh6 -- V5.0
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 Max" ; "A" ;"^2 _SHA%u (.*)" ;doWitness ;"Witness Role" ;"Std+" }) -- V1.6 for FH V6.0 Rule index "2 _SHAR" and "2 _SHAN"
addRule({"Witness Role" ;"Move to Fact Note Min" ; "F" ;"^2 _SHA%u (.*)" ;doWitness ;"Witness Role" ;"TNG-" }) -- V5.6 for TNG with only Principal in synthetic Fact Witness Fact Note
addRule({"Witness Role" ;"Remove entirely" ; "C" ;"^(2) _SHA%u .*" ;doRemoveAll ;nil ;"Std- FTL tng" }) -- 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" }) -- 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({"Place Record" ;"Make Location Record" ; "H" ;"^0 (@P%d+@ )(.*)" ;doLocation ;nil ;"GKP" }) -- V6.0 GKP
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({"Date Field" ;"BET/AND to Q1-4 yyyy" ; "H" ;"^%d DATE .*" ;doDateField ;"Quarters" ;" " }) -- V5.1 BET/AND => Q1-4 yyyy
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 GKP 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 GKP 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 (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 (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({"Standard Facts" ;"Blank Date,Place,Note" ; "p" ;"^1 ([A-Z]+) ?(.*)";doBlankFact ;nil ;"ANC " }) -- V5.9 ANC Fact with no Date, Place, Note, etc needs a local Note
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 GKP GSP GWC HER LFT MFT MYH 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 -- V5.1 ANC -- TNG see http://www.tng.lythgoes.net/wiki/index.php?title=Desktop_gotchas#Family_Historian_.28FH.29 -- V6.0 GKP MYH=EVEN
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 MYH RMT TNG ZPG " }) -- V4.0 -- V4.4 GWC -- V5.1 ANC -- V6.0 MYH=EVEN
addRule({"Custom Attribute" ;"Keep Standard Tag" ; "C" ;"^1 FACT" ;"1 FACT" ;nil ;"Str AGS FTM GKP GSP GST HER MFT RFT CEB " }) -- V4.0 GEDCOM 5.5.1 -- V6.0 GKP
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" }) -- V2.6 -- V3.1 gst & fh5 -- V4.0 fh6 -- V5.6 gst removed
addRule({"Same Sex Wife" ;"Asymmetric Format" ; "a" ;"^1 (WIFE) (.*)" ;doSameGender;"1 HUSB " ;"Std fh5 fh6" }) -- V2.6 -- V3.1 gst & fh5 -- V4.0 fh6 -- V5.6 gst removed
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({"Baptism Event" ;"Change to Christening" ; "a" ;"^1 BAPM" ;doBapm2Chr ;nil ;"TNG " }) -- V5.1 TNG
addRule({"Census Event" ;"Copy T f S to Note" ; "a" ;"^%d CENS" ;doTfS2Note ;nil ;" " }) -- V5.3 Option for TNG, etc
addRule({"Census Event" ;"Change to Residence" ; "a" ;"^1 CENS" ;doCens2Resi ;nil ;"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 GKP LFT MFT RFT TNG CEB" }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT LFT MFT TNG -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP LFT MFT RFT TNG CEB" }) -- V2.0 -- V2.8 Str Mdr -- V3.3 -- V3.8 AGS RFT CEB -- V4.0 GFT LFT MFT TNG -- V6.0 GKP
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 GKP 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 -- V6.0 GKP
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 GKP 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? -- V6.0 GKP
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 GKP 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? -- V6.0 GKP
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 GKP LFT TNG" }) -- V4.0 FH5 FH6 LFT TNG -- V6.0 GKP
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 GKP LFT TNG" }) -- V4.0 FH5 FH6 LFT TNG -- V6.0 GKP
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 ;"Family Note" ;" " }) -- V3.3
addRule({"Record Note" ;"Move to a Note Fact" ; "F" ;"^1 NOTE (.*)" ;doNote2Fact ;"Record Note" ;"ANC-" }) -- V5.9 ANC- Custom Fact Note
addRule({"Record Note" ;"Citation Note" ; "D" ;"^(1) NOTE (.*)" ;doCiteNote ;"Record Note" ;"ANC+" }) -- V3.3 -- V2.2 ANC -- V5.9 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 -- V5.9 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
addRule({"Subsidiary Note" ;"Weblink to WebTag URL" ; "G" ;"^(%d) NOTE (.*)" ;doWebTags ;nil ;"RMT+" }) -- V5.3
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"; };
CROP = { Lab="Caption Note"; Mode="CROP"; Pref=" _PRIM Y"; };
}
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 ;"GKP" }) -- V1.3 Rule index " OBJE" -- V2.5 L="Caption Note"; -- V6.0 GKP
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
addRule({"Media Object" ;"Media Conversion" ; "w" ;"^(%d) OBJE(.*)" ;doObjectId ;dicCaption.CROP ;"TNG" }) -- V1.3 Rule index " OBJE" -- V2.5 L="Caption Note"; -- V2.1 & V6.2 TNG
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 GKP GSP HER MFT MYH 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 -- V6.0 GKP MYH
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 GKP GSP HER MFT MYH 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 -- V6.0 GKP MYH
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({"Templated Source" ;"Convert to Notes" ; "A" ;"^1 _SRCT (.*)" ;doSrcTempLnk;"Metafield" ;"Std+" }) -- V4.0 Change all Source Template links to Note record links & _FIELD to Metafield labelled Notes & add Footnote, Short Footnote & Bibliography labelled Notes -- V5.5
addRule({"Templated Source" ;"Remove entirely" ; "B" ;"^1 _SRCT (.*)" ;doSrcTempLnk;nil ;"Std-" }) -- V4.0 Remove all Source Template links
addRule({"Templated Source" ;"Keep Templated Source" ; "C" ;"^1 _SRCT" ;"1 _SRCT" ;nil ;"GST+" }) -- V4.0 Retain all Source Template links
addRule({"Templated Source" ;"Footnote to Title" ; "D" ;"^1 _SRCT (.*)" ;doSrcTempLnk;"Reformat" ;"RMT+ TNG+" }) -- V4.2 As "Move into Notes" & put Footnote in Title & Title in Short Title -- V5.3 default RMT+ -- V5.5 default TNG+
addRule({"Templated Source" ;"Handle Citation Field" ; "p" ;"^(%d) _FIELD .*" ;doSrcTempCit;"Metafield" ;"Std " }) -- V4.2 Convert Templated Source Citations according to above choice
addRule({"Templated Source" ;"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" ;"Move to Task Record" ; "D" ;"^(%d) _RNOT (.*)" ;doResNoteLnk;"Task Record" ;" " }) -- V6.0 Change all Research Note links to Note record links with Task Records linked to Note -- GKP
addRule({"Research Note" ;"Move to Researches" ; "E" ;"^(%d) _RNOT (.*)" ;doResNoteLnk;"Researches" ;"GKP+" }) -- V6.0 Change all Research Note links to Note record links with Research Records linked to Note -- GKP
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({"Text From Source" ;"Weblink to WebTag URL" ; "q" ;"^(%d) TEXT (.*)" ;doWebTags ;nil ;"RMT+" }) -- V5.3 RMT
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" ; "a" ;"^(%d) _QUAY (.*)" ;doAssess ;nil ;"Std gst" }) -- V4.0 Convert FH V7 _QUAY Assessment to GEDCOM standard QUAY -- V5.6 Add option to inhibit
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 -- V5.0 -- Use IntMode, StrAbbr, StrMode, StrName, StrNode, IntNode, StrFull below
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(StrName:lower()) ) -- Include rule for "Std" unless excluded by lower-case mode such as "fh5 ftl gft" -- V5.0 StrName instead of StrAbbr
or strRule:matches(StrNode) -- V5.0 -- StrNode instead of StrMode
or strRule:match(" "..StrName.." ") then -- Include rule if its GEDCOM mode matches current GEDCOM export mode -- V5.0 StrName instead of StrAbbr
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 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@"
or dicRule[strKey:gsub("1 [A-Z]+","1 AZ")] -- Special all Facts rule for ANC -- V5.9
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,dicSour -- Return function methods & dicSour -- V5.5
end -- function MakeRules
function GUI_MainDialogue() -- Graphical User Interface
progbar.Setup() -- Popup in middle
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 == "-" or strValue == "OFF" then -- "-" for Full/Brief or "OFF" for Project/Global -- V5.0
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
StrGedcomExportMode() -- GEDCOM destination = Std+, FH5+, FTM-, FMP-, etc, Abbr = Std, FH5, FTM, TNG, etc. -- V3.3
-- Create Basic Options tab 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 Destination:"; } -- V3.3 -- V4.0 -- V5.0 -- was "GEDCOM Export Mode:"
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 lblAdviceL = iup.label { Alignment="ALEFT:ACENTER"; Title="See media image "; } -- V5.0
local lblAdviceC = iup.label { Alignment="ALEFT:ACENTER"; Title="Shared Settings "; } -- V5.0
local lblAdviceR = iup.label { Alignment="ALEFT:ACENTER"; Title="below"; } -- V5.0
local boxAdvice = iup.hbox { lblAdviceL; lblAdviceC; lblAdviceR; Expand="NO"; } -- V5.0
local lblObject = iup.label { Alignment="ARIGHT:ATOP"; Title="Multimedia Preference:"; } -- V3.3 -- V4.0 -- V5.0 -- was "Multimedia Conversion:"
local lstObject = iup.list { DropDown="YES"; Visible_Items="99"; }
local boxObject = iup.hbox { Homogeneous="YES"; iup.hbox { boxAdvice; lblObject; Margin="0x0"; }; lstObject; } -- V4.0 -- V5.0 -- V5.6
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 Destination (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 lblMaxUsed = iup.label { Alignment="ARIGHT:ATOP"; Title="Maximum times the same Rule is listed in Result Set:"; } -- V4.0 -- V5.0
local txtMaxUsed = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinAlign="RIGHT"; SpinInc=1; SpinMin=3; SpinMax=999; }
local boxMaxUsed = iup.hbox { lblMaxUsed; txtMaxUsed; Expand="NO"; Gap="6"; Margin="12x0"; } -- V5.0
local btnChoose = iup.button{ Title="Choose Export Folder"; }
local btnDelete = iup.button{ Title="Empty Export Folder"; }
local boxButtons = iup.hbox { Homogeneous="YES"; btnChoose; btnDelete; Gap="12"; Margin="12x12"; } -- V5.0
local boxSetLeft = iup.vbox { boxMaxUsed; boxButtons; Gap="0"; Margin="0x0"; } -- V5.0
local lblMaxHigh = iup.label { Alignment="ARIGHT:ATOP"; Title="Max image 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 image width:"; } -- V4.0
local txtMaxWide = iup.text { Alignment="ARIGHT"; Spin="YES"; SpinAlign="RIGHT"; SpinInc=1; SpinMin=100; SpinMax=9999; } -- V3.7
local boxMaxSize = iup.hbox { lblMaxHigh; txtMaxHigh; lblMaxWide; txtMaxWide; Expand="NO"; Gap="6"; } -- V5.0
local tglFrames = iup.toggle{ Title=" Use the JPEG file format for all exported image files"; } -- V3.7 -- V5.0
local tglFullOn = iup.toggle{ Title=" Apply the settings above to all image files not just part frames"; } -- V3.7 -- V5.0
local lblNaming = iup.label { Alignment="ALEFT:ACENTER"; Title="Part Frame File Naming Format:"; } -- V5.0
local lstNaming = iup.list { DropDown="YES"; Expand="YES"; TblName[1]; TblName[2]; TblName[3]; }
local boxNaming = iup.hbox { lblNaming; lstNaming; Expand="NO"; Gap="6"; } -- V5.0
local boxSetRight= iup.vbox { boxMaxSize; tglFrames; tglFullOn; boxNaming; Gap="1"; } -- V5.0
local boxSelect = iup.hbox { Homogeneous="YES"; boxSetLeft; boxSetRight; Gap="10"; Margin="0x0"; } -- V3.7
local lblFolder = iup.label { Alignment="ALEFT"; Title=TblOption.Path; }
local tglDirSep = iup.toggle{ Title=" Use / path separators"; } -- V6.0
local tglFolder = iup.toggle{ Title=" Replicate Media folders"; } -- V2.9
local boxFolder = iup.hbox { lblFolder; tglDirSep; tglFolder; Gap="10"; Margin="10x2"; } -- V2.9 -- V5.0 -- V6.0
local boxShared = iup.vbox { boxSelect; boxFolder; Margin="2x2"; } -- V2.9 -- V5.0
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 { iup.label{Expand="YES"}; btnExport; iup.label{Expand="YES"}; Margin="2x12"; }
-- Create Extra Options tab GUI controls
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"; }
-- Create Other Options tab GUI controls
local tglMakeDNA = iup.toggle{ Title=" Make missing Birth/Death events for DNA products"; } -- V5.0
local btnOthers = iup.button{ Title="Reset these (TBD) Options"; }
-- Create Labels Set A/B tabs GUI controls
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
-- Create Manage Settings tab GUI controls -- V5.0 -- New Manage Settings tab
local lblWhere = iup.label { Alignment="ACENTER"; Title="Choose where the Options and Labels tab settings are held for the current Project:"; }
local tglProj = iup.toggle{ Title="Project local data "; }
local tglGlob = iup.toggle{ Title="Global ProgramData "; }
local radWhere = iupRadio ( TblOption.Proj, tglProj, tglGlob )
local btnPrj2Glb = iup.button{ Title="Copy all the settings FROM this Project local data TO the Global ProgramData"; }
local btnGlb2Prj = iup.button{ Title="Copy all the settings FROM the Global ProgramData TO this Project local data"; }
local lblCopyOpt = iup.label { Alignment="ACENTER"; Title="Copy the Options tab settings for current GEDCOM destination: "; }
local btnCEA = { }
btnCEA.Put = iup.button{ Title="Copy the settings from (XYZ) to (CEA)"; }
btnCEA.Get = iup.button{ Title="Copy the settings from (CEA) to (XYZ)"; }
btnCEA.Set = iup.button{ Title="Reset settings for (CEA) to default Std"; }
local btnCEB = { }
btnCEB.Put = iup.button{ Title="Copy the settings from (XYZ) to (CEB)"; }
btnCEB.Get = iup.button{ Title="Copy the settings from (CEB) to (XYZ)"; }
btnCEB.Set = iup.button{ Title="Reset settings for (CEB) to default Str"; }
local boxPutCE = iup.hbox { btnCEA.Put; btnCEB.Put; Margin="0x0"; }
local boxGetCE = iup.hbox { btnCEA.Get; btnCEB.Get; Margin="0x0"; }
local boxSetCE = iup.hbox { btnCEA.Set; btnCEB.Set; Margin="0x0"; }
-- Create the common buttons GUI controls
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 boxManage = iup.vbox { Gap="10"; Margin="110x10"; Alignment="ACENTER"; lblWhere; radWhere; btnPrj2Glb; btnGlb2Prj; lblCopyOpt; boxPutCE; boxGetCE; boxSetCE; } -- V5.0
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
boxManage; TabTitle5=" Manage Settings "; -- V5.0
}
if IntFhVersion > 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,dicSour = 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() -- Save savings adjusted to rule letter
local intText = 0
if tblMode["%d NOTE"].Item[tblMode["%d NOTE"].Pick]:match(" WebTag ") then
intText = 1 -- Ensure %d TEXT rule follows %d NOTE rule for _WEBTAG URL -- V5.3
end
tblMode["%d TEXT"].Pick = intText
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
local strPath = TblOption.Path
local intPath = #strPath
if intPath > 80 then -- Constrain length of Export folder path -- V5.0
local arrDir = strPath:split("\\")
local intDir = 1
local intSum = 0
repeat
local strDir = arrDir[intDir]
intPath = intPath - #strDir - 1 -- Subtract each subfolder length from path length
if intSum <= 20 then
intSum = intSum + #strDir + 1 -- Retain at least 20 characters of leading path
intDir = intDir + 1
else
if intPath > 80 then
table.remove(arrDir,intDir) -- Remove subfolders from path to reduce length below 80 characters
else
arrDir[intDir] = " ... " -- Insert ellipsis in place of removed subfolders
break
end
end
until false
strPath = table.concat(arrDir,"\\") -- Rebuild constrained path
end
lblFolder.Title = strPath -- V5.0
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 setControlsActive(strYorN) -- Enable/Disable controls during busy actions
-- strYorN ~ "YES" or "NO"
tabControl.Active = strYorN
boxButtons.Active = strYorN
doUpdateFolder() -- V5.1
if StrAbbr:match("CE[AB]") then -- Disable label when CEA/B current -- V5.0
lblCopyOpt.Active = "NO"
else
lblCopyOpt.Active = strYorN
end
for strAbbr, dicBtn in pairs ({ CEA=btnCEA; CEB=btnCEB; }) do -- Check the CEA and CEB buttons -- V5.0
if ( TblOption[strAbbr].Mode or StrAbbr ) ~= StrAbbr
or StrAbbr:match("CE[AB]") then -- Disable copy to CEA/B buttons -- V5.0
dicBtn.Put.Active = "NO"
else
dicBtn.Put.Active = strYorN
end
if TblOption[strAbbr].Mode == StrAbbr then -- Enable copy from or reset CEA/B -- V5.0
dicBtn.Get.Active = strYorN
dicBtn.Set.Active = strYorN
else
dicBtn.Get.Active = "NO"
dicBtn.Set.Active = "NO"
end
end
if type(TblOption.Obje) ~= "number" then
--? fhMessageBox("setControlsActive(strYorN) TblOption.Obje = "..(TblOption.Obje or "nil"),"MB_OK","MB_ICONEXCLAMATION") --?
TblOption.Obje = ""
end
local strObje = TblObje[tonumber(TblOption.Obje) or 1].Abbr
if strObje:match("^FILE~") -- Shared Settings for Media files only apply to exported images -- V5.0
or strObje:match("^WIPE~") then
strYorN = "NO" -- Disable the Shared Settings for Media files -- V5.0
end
boxSetRight.Active = strYorN -- Max height/width, JPEG toggle, Apply to all, Frame Naming -- V5.0
tglFolder .Active = strYorN -- Keep Media folders -- V5.0
end -- local function setControlsActive
local function setFullMode() -- Set Full v Brief radio toggles -- V3.3 -- V5.0
tglFull.Tip = tglFull.Tip
tglTrim.Tip = tglTrim.Tip -- Refresh XP Tooltips
TblOption[StrAbbr] = TblOption[StrAbbr] or {} -- V5.0
if tglFull.Value == "ON" then
TblOption[StrAbbr].Full = "+"
else
TblOption[StrAbbr].Full = "-"
end
StrGedcomExportMode() -- V5.0 -- Use IntMode, StrMode, StrAbbr, StrFull
if not TblOption[StrMode] then
ResetGedcomOptions(TblMode[IntMode],strFull)
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
StrGedcomExportMode() -- V3.3 -- V5.0 -- Use StrMode, StrAbbr
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() -- V3.3 -- V5.0
btnBasics.Title = "Reset these Destination ("..StrMode..") Options" -- Update reset button Title for current GEDCOM Destination -- V4.0 -- V5.0
local intNode = TblMode[TblOption["CEA"].Mode or "CEA"]
lstGedcom[TblMode["CEA"]] = " (CEA) "..TblMode[intNode].Full -- Update the GEDCOM destination CEA droplist entry
local intNode = TblMode[TblOption["CEB"].Mode or "CEB"]
lstGedcom[TblMode["CEB"]] = " (CEB) "..TblMode[intNode].Full -- Update the GEDCOM destination CEB droplist entry
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
tglDirSep.Value = TblOption.Seps -- V6.0
tglFolder.Value = TblOption.Keep -- V2.9
txtMaxHigh.SpinValue = TblOption.High -- Including spin values
txtMaxWide.SpinValue = TblOption.Wide
txtMaxUsed.SpinValue = TblOption.Used
setControlsActive("YES") -- Shared Settings for Media files -- V5.0
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
tglMakeDNA.Value = TblOption[StrMode].Make -- V5.0
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
radWhere.Value = iupValue(TblOption.Proj, tglProj, tglGlob) -- V5.0 From here on
lblCopyOpt.Title = "Copy the Options tab settings for current GEDCOM destination: "..lstGedcom[lstGedcom.Value]
for strAbbr, dicBtn in pairs ({ CEA=btnCEA; CEB=btnCEB; }) do -- Set the CEA and CEB button titles -- V5.0
local strTarg = TblOption[strAbbr].Mode or "n/a"
local strSour = TblOption[strAbbr].Mode or StrAbbr
if strSour == strAbbr then strSour = "n/a" end
dicBtn.Put.Title = "Copy the settings from ("..strSour..") to ("..strAbbr..")"
dicBtn.Get.Title = "Copy the settings from ("..strAbbr..") to ("..strTarg..")"
end
doSaveSettings() -- Save sticky data settings
end -- local function setControls
local tipAdvice = "See the Shared Settings options for Media images below"
local tipResets = "Reset these options to defaults for the GEDCOM destination product"
-- 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, and label options, or manage settings"; };
[tglFull] = { "FontHead"; "Safe"; "0x0"; "Comprehensive Project GEDCOM details"; }; -- V3.3
[tglTrim] = { "FontHead"; "Safe"; "0x0"; "Abbreviated Project GEDCOM details"; }; -- V3.3
[lblAdviceL]= { "FontBody"; "Body"; "0x0"; tipAdvice; };
[lblAdviceC]= { "FontHead"; "Body"; "0x0"; tipAdvice; };
[lblAdviceR]= { "FontBody"; "Body"; "0x0"; tipAdvice; };
[lblGedcom] = { "FontHead"; "Body"; "4x2"; "Choose the GEDCOM export rules for destination product"; };
[lstGedcom] = { "FontBody"; "Safe"; "0x0"; "Choose the GEDCOM export rules for destination product"; };
[lblObject] = { "FontHead"; "Body"; "4x2"; "Choose the Multimedia preference for destination product"; };
[lstObject] = { "FontBody"; "Safe"; "0x0"; "Choose the Multimedia preference for destination product"; };
[lblEncode] = { "FontHead"; "Body"; "4x2"; "Choose GEDCOM character encoding for destination product"; };
[lstEncode] = { "FontBody"; "Safe"; "0x0"; "Choose GEDCOM character encoding for destination product"; };
[lblPrivate]= { "FontHead"; "Body"; "4x2"; "Choose whether to exclude [[private]] text for destination product"; }; -- V3.2
[lstPrivate]= { "FontBody"; "Safe"; "0x0"; "Choose whether to exclude [[private]] text for destination product"; }; -- V3.2
[lblRichtxt]= { "FontHead"; "Body"; "4x2"; "Choose the rich text formatting for destination product"; }; -- V4.0
[lstRichtxt]= { "FontBody"; "Safe"; "0x0"; "Choose the rich text formatting for destination product"; }; -- V4.0
[btnBasics] = { "FontBody"; "Safe"; "0x0"; tipResets; }; -- V4.0
[frmShared] = { "FontHead"; "Body"; false; };
[lblMaxUsed]= { "FontBody"; "Body"; "0x0"; "Set display limit for same Rule listed in Result Set"; };
[txtMaxUsed]= { "FontBody"; "Safe"; "0x0"; "Set display limit for same Rule listed in Result Set"; };
[lblMaxHigh]= { "FontBody"; "Body"; "0x0"; "Set Multimedia file maximum pixel height for all destinations"; };
[txtMaxHigh]= { "FontBody"; "Safe"; "0x0"; "Set Multimedia file maximum pixel height for all destinations"; };
[lblMaxWide]= { "FontBody"; "Body"; "0x0"; "Set Multimedia file maximum pixel width for all destinations"; };
[txtMaxWide]= { "FontBody"; "Safe"; "0x0"; "Set Multimedia file maximum pixel width for all destinations"; };
[tglFrames] = { "FontBody"; "Safe"; "0x0"; "Use JPEG format where possible for all image files for all destinations"; }; -- V1.4 -- V3.7
[tglFullOn] = { "FontBody"; "Safe"; "0x0"; "Choose to include all image files in these settings for all destinations,\n otherwise only part-frame files are included"; }; -- V3.7
[lblNaming] = { "FontBody"; "Body"; "0x0"; "Choose the part-frame file naming format for all destinations"; };
[lstNaming] = { "FontBody"; "Safe"; "0x0"; "Choose the part-frame file naming format for all destinations"; };
[btnChoose] = { "FontBody"; "Safe"; "0x8"; "Choose GEDCOM and Media files export folder path for all destinations"; }; -- V4.0 was "1x1"
[btnDelete] = { "FontBody"; "Risk"; "0x8"; "Empty GEDCOM and Media files from export folder for all destinations"; }; -- V4.0 was "1x1"
[lblFolder] = { "FontHead"; "Body"; "0x0"; "Current GEDCOM and Media files export folder path for all destinations"; };
[tglDirSep] = { "FontBody"; "Safe"; "0x0"; "Choose to use MacOS/Unix style / path separators in export folder"; }; -- V6.0
[tglFolder] = { "FontBody"; "Safe"; "0x0"; "Choose whether to replicate Media subfolders in the export folder"; }; -- 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
[tglMakeDNA]= { "FontBody"; "Safe"; "0x0"; "Synthesise Birth/Death events for DNA from Baptism, Christening, Burial, Cremation, etc."; }; -- V5.0
[btnExtras] = { "FontBody"; "Safe";"34x2"; tipResets; };
[btnOthers] = { "FontBody"; "Safe";"34x2"; tipResets; };
[btnLabelA] = { "FontBody"; "Safe"; "0x0"; "Reset these options to defaults for all GEDCOM destination products"; }; -- V3.3
[btnLabelB] = { "FontBody"; "Safe"; "0x0"; "Reset these options to defaults for all GEDCOM destination products"; }; -- V3.3
[lblWhere] = { "FontHead"; "Body"; "0x0"; "Where the current Options and Labels tab settings are held for this project"; }; -- V5.0 hereon
[tglProj] = { "FontHead"; "Safe"; "0x0"; "Employ the Project local data"; };
[tglGlob] = { "FontHead"; "Safe"; "0x0"; "Employ the Global ProgramData"; };
[btnPrj2Glb]= { "FontBody"; "Safe"; "0x0"; "Copy all the settings in this Project to the Global ProgramData"; };
[btnGlb2Prj]= { "FontBody"; "Safe"; "0x0"; "Copy all the settings in the Global ProgramData to this Project"; };
[lblCopyOpt]= { "FontHead"; "Body"; "0x12";"Copy the Options tab settings of destination product to/from CEA/B"; };
[btnCEA.Put]= { "FontBody"; "Safe"; "0x0"; "Copy from product settings to (CEA) Custom Export Alpha"; };
[btnCEA.Get]= { "FontBody"; "Safe"; "0x0"; "Copy from (CEA) Custom Export Alpha to product settings"; };
[btnCEA.Set]= { "FontBody"; "Safe"; "0x0"; "Reset (CEA) Custom Export Alpha to default Std settings"; };
[btnCEB.Put]= { "FontBody"; "Safe"; "0x0"; "Copy from product settings to (CEB) Custom Export Bravo"; };
[btnCEB.Get]= { "FontBody"; "Safe"; "0x0"; "Copy from (CEB) Custom Export Bravo to product settings"; };
[btnCEB.Set]= { "FontBody"; "Safe"; "0x0"; "Reset (CEB) Custom Export Bravo to default Str settings"; };
[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 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 getTreeID(strAbbr) -- Report Tree ID for products supporting hyperlinks -- V5.6
local strTreeID = ""
if strAbbr == "TNG" or strAbbr == "AnyOther" then
local ptrHead = fhNewItemPtr()
ptrHead:MoveToRecordById("HEAD",0)
strTree = fhGetItemText(ptrHead,"~.DEST") or "" -- Get the Tree ID for hyperlinks from Header Destination -- V5.6
strTree = strTree:match(strAbbr.." -[:=] -(%w+)")
strTreeID = strTree
if not strTreeID then strTreeID = "missing" end
strTreeID = " Tree ID is "..strTreeID
end
return strTreeID
end -- local function getTreeID
local function doPopulateDropLists() -- Populate all the GUI droplists with item choices
local dicFull = {}
for intMode, tblMode in ipairs ( TblMode ) do -- Loop through GEDCOM destinations and cater for CEA/B current product -- V5.0
local strFull = tblMode.Full
local strAbbr = tblMode.Abbr
local strMode = TblOption[strAbbr].Mode -- V5.0
if strMode then
strFull = TblMode[TblMode[strMode]].Full -- Lookup its full name
end
strFull = strFull..getTreeID(strAbbr) -- Append its Tree ID -- V5.6
lstGedcom[intMode] = " ("..strAbbr..") "..strFull -- 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{Alignment="ABOTTOM";Expand="NO";Gap="12";btnExtras;tglTidying;tglFactDef;tglPrefix;tglCaption;}) -- 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
if boxLeft then
iup.Append(boxOther,iupHbox(boxLeft,iup.hbox{})) -- Create remaining odd left option box with blank on right -- V5.0
end
iup.Append(boxOther,iup.hbox{Alignment="ABOTTOM";Expand="NO";Gap="280";btnOthers;tglMakeDNA;}) -- Add reset button and Make Birth/Death DNA toggle at bottom -- V5.0
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 -- Add reset button before last/dummy box on Label Set A tab -- V4.0
boxRight = boxLeft
boxLeft = iupHbox(btnLabelA,iup.hbox{})
iup.Append(boxLabel,iupHbox(boxLeft,boxRight or iup.hbox{}))
boxLeft = nil
boxLabel = boxLab_B -- Select Labels Set B tab -- V4.0
end
end
boxRight = boxLeft
boxLeft = iupHbox(btnLabelB,iup.hbox{})
iup.Append(boxLabel,iupHbox(boxLeft,boxRight or iup.hbox{})) -- Add reset button before 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
local function doResetRules() -- Reset the Rules and Controls common action -- V5.0 applies widely
tblMode = setRules() -- Set default dictionary of Rules for current GEDCOM mode
getSpinValues()
setControls() -- Set the Controls and redisplay dialogue
iup_gui.ShowDialogue("Main")
end -- local function doResetRules
function tglFull:action(intState) -- Action for Full v Brief radio toggle -- V3.3
setControlsActive("NO")
setFullMode() -- V5.0
doResetRules() -- V5.0 -- Set default dictionary of Rules for chosen Full v Trim mode
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
StrGedcomExportMode() -- V3.3 -- V5.0 -- Use IntMode, StrMode, StrAbbr, StrName, StrNode, IntNode, StrFull
doResetRules() -- V5.0
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"..fhConvertANSItoUTF8(strPath).."\n")
else
TblOption.Path = strPath -- Update chosen export folder path
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 -- V6.0
local arrFolder = {} -- V5.0
local strExport = TblOption.Path.."\\Media\\"
for _, dicFile in ipairs(general.GetFolderContents(strFolder,true)) do
local strFile = dicFile.path
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 dicFile.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,iup_gui.MemoDialogue) -- Delete only export Media & Gedcom files -- V5.0
end
elseif dicFile.mode == "directory" then
table.insert(arrFolder,strFile) -- Remember all export Media subfolders -- V5.0
end
end
end
for _, strFolder in ipairs(arrFolder) do -- V5.0
general.DeleteFolder(strFolder,iup_gui.MemoDialogue) -- Remove all export Media subfolders -- V5.0
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 tglDirSep:action(intState) -- Action for folder separator toggle -- V6.0
setControlsActive("NO")
TblOption.Seps = tglDirSep.Value
doSaveSettings()
setToolTip(tglDirSep)
setControlsActive("YES")
end -- function tglDirSep: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
function btnExport:action() -- Action for Export GEDCOM & Media button
setControlsActive("NO")
local isOK = false
if StrName == "FMP" -- StrName instead of TblMode[TblOption.Mode].Abbr i.e. StrAbbr -- V5.0
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
StrReformat = "" -- V5.3
getSpinValues()
doSaveSettings()
setRules(tblMode) -- Reset the Rules according to existing tblMode
isOK = ExportGedcom(doAnalyse,useRules,dicSour) -- Export GEDCOM file using the Analyse & Rules functions
if isOK then return iup.CLOSE end
setControlsActive("YES")
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
function tglMakeDNA:action(intState) -- Action for make Birth/Death events DNA toggle -- V5.0
setControlsActive("NO")
TblOption[StrMode].Make = tglMakeDNA.Value
doSaveSettings()
setToolTip(tglMakeDNA)
setControlsActive("YES")
end -- function tglMakeDNA:action
local function doResetTab(tblTab) -- Reset the Extra/Other Options tab Rules
for intTag, strTag in ipairs ( tblTab ) do
TblOption[StrMode][strTag] = nil -- V5.0 -- StrMode
end
doResetRules() -- V5.0 -- Set default dictionary of Rules for current GEDCOM mode
end -- local function doResetTab
function btnBasics:action() -- Action for Reset Options button on Basic Options tab -- V4.0
setControlsActive("NO") -- V5.0 -- Use StrAbbr, IntMode
ResetGedcomOptions(TblMode[IntMode],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
if strItem ~= "Make" then
TblOption[StrMode][strItem] = TblMode[IntNode][strItem] -- V5.0 -- Use StrMode, IntNode
end
end
doResetTab(tblMode.Drop)
setControlsActive("YES")
end -- function btnExtras:action
function btnOthers:action() -- Action for Reset Options button on Other Options tab
setControlsActive("NO")
TblOption[StrMode].Make = TblMode[IntNode].Make -- V5.0 -- Use StrMode, IntNode
doResetTab(tblMode.Togg)
setControlsActive("YES")
end -- function btnOthers:action
local function doResetLabels() -- Reset all synthetic Labels options -- V4.0
setControlsActive("NO")
ResetSyntheticLabels()
doResetRules() -- V5.0 -- Set default dictionary of Rules for default GEDCOM mode
setControlsActive("YES")
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 tglProj:action(intState) -- Action for Project v Global radio toggle -- V5.0
setControlsActive("NO")
tglGlob.Tip = tglGlob.Tip
tglProj.Tip = tglProj.Tip -- Refresh XP Tooltips
local strProject = TblOption.Proj
TblOption.Proj = tglProj.Value
getSpinValues()
SaveSettings(strProject) -- Save settings in old location with new TblOption.Proj value
LoadSettings(TblOption.Proj) -- Load settings in new location
setControls()
doResetRules()
setControlsActive("YES")
end -- function tglProj:action
function btnPrj2Glb:action() -- Action for Copy settings from Project to Global button -- V5.0
setControlsActive("NO")
if TblOption.Proj == "ON" then
SaveSettings( "OFF" ) -- Save current Project TblOption to Global file
else
LoadSettings( "ON" ) -- Load Project file to current Global TblOption
TblOption.Proj = "OFF"
setControls()
doResetRules() -- Set default dictionary of Rules for current GEDCOM mode
end
setControlsActive("YES")
end -- function btnPrj2Glb:action
function btnGlb2Prj:action() -- Action for Copy settings from Global to Project button -- V5.0
setControlsActive("NO")
if TblOption.Proj == "OFF" then
SaveSettings( "ON" ) -- Save current Global TblOption to Project file
else
LoadSettings( "OFF" ) -- Load Global file to current Project TblOption
TblOption.Proj = "ON"
setControls()
doResetRules() -- Set default dictionary of Rules for current GEDCOM mode
end
setControlsActive("YES")
end -- function btnGlb2Prj:action
local function doCopySettings(strSource,strTarget) -- Copy settings from source to target product -- V5.0
setControlsActive("NO")
local tblOption = TblOption
for _, strTail in ipairs ( { ""; "+"; "-"; } ) do
for j, k in pairs (TblOption[strSource..strTail]) do -- Copy settings from source to target
TblOption[strTarget..strTail][j] = k
end
end
if strTarget == StrAbbr then
doResetRules() -- If Target is current GEDCOM mode, set default dictionary of Rules
else
TblOption[strTarget].Mode = strSource -- If Target is CEA/B then simply update its mode
setControls()
end
setControlsActive("YES")
end -- local function doCopySettings
function btnCEA.Put:action() -- Action for Copy the settings to (CEA) button -- V5.0
doCopySettings(StrAbbr,"CEA")
end -- function btnCEA.Put:action
function btnCEA.Get:action() -- Action for Copy the settings from (CEA) button -- V5.0
doCopySettings("CEA",StrAbbr)
end -- function btnCEA.Get:action
function btnCEA.Set:action() -- Action for Reset settings for (CEA) button -- V5.0
setControlsActive("NO")
TblOption["CEA"] = {}
TblOption["CEA+"] = {}
TblOption["CEA-"] = {}
ResetGedcomOptions(TblMode[TblMode["CEA"]])
setControls() -- Reset controls & redisplay Main dialogue
setControlsActive("YES")
end -- function btnCEA.Set:action
function btnCEB.Put:action() -- Action for Copy the settings to (CEB) button -- V5.0
doCopySettings(StrAbbr,"CEB")
end -- function btnCEB.Put:action
function btnCEB.Get:action() -- Action for Copy the settings from (CEB) button -- V5.0
doCopySettings("CEB",StrAbbr)
end -- function btnCEB.Get:action
function btnCEB.Set:action() -- Action for Reset settings for (CEB) button -- V5.0
setControlsActive("NO")
TblOption["CEB"] = {}
TblOption["CEB+"] = {}
TblOption["CEB-"] = {}
ResetGedcomOptions(TblMode[TblMode["CEB"]])
setControls() -- Reset controls & redisplay Main dialogue
setControlsActive("YES")
end -- function btnCEB.Set: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
doResetRules() -- V5.0 -- Set default dictionary of Rules for default GEDCOM mode
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"; "-manage-settings-tab"; } -- V5.1
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 Destination -- V5.0 StrMode
end
if intNew == 2 then
btnOthers.Title = "Reset these ("..StrMode..") Options" -- Update reset button Title for current GEDCOM Destination -- V5.0 StrMode
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
function ClosingMessage(strOption,strExpNam,strExport) -- V5.6
local lblOption = iup.label { Font=iup_gui.FontBody; FgColor=iup_gui.Body; Alignment="ARIGHT:ATOP"; Title=strOption.." file has been written to the export folder as:"; }
local lblExpNam = iup.label { Font=iup_gui.FontBody; FgColor=iup_gui.Body; Alignment="ARIGHT:ATOP"; Title=strExpNam; }
local lblReform = iup.label { Font=iup_gui.FontHead; FgColor=iup_gui.Risk; Alignment="ARIGHT:ATOP"; Title=StrReformat; }
local timReform = iup.timer { Time=500; action_cb=function() if lblReform.FgColor == iup_gui.Risk then lblReform.FgColor = iup_gui.Body else lblReform.FgColor = iup_gui.Risk end end; }
local lblChoose = iup.label { Font=iup_gui.FontBody; FgColor=iup_gui.Body; Alignment="ARIGHT:ATOP"; Title=" When diagnosing exported GEDCOM problems it may sometimes be useful to \n view the exported file in Family Historian, Notepad, or Windows/File Explorer. "; }
local lblBeware = iup.label { Font=iup_gui.FontHead; FgColor=iup_gui.Risk; Alignment="ARIGHT:ATOP"; Title=" BEWARE: Using Family Historian may disrupt the GEDCOM file format ! ! ! "; }
local lblSepLine = iup.label { Separator="HORIZONTAL"; }
local btnFamHist = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Risk; MinSize="80"; Padding=iup_gui.Margin; Title="Family\nHistorian"; action=function() timReform.Run = "NO" general.DoExecute(strExport) return iup.CLOSE end; }
local btnNotepad = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Warn; MinSize="80"; Padding=iup_gui.Margin; Title="Windows\nNotepad"; action=function() timReform.Run = "NO" general.DoExecute("notepad.exe",strExport) return iup.CLOSE end; }
local btnExplore = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Warn; MinSize="80"; Padding=iup_gui.Margin; Title="Win/File\nExplorer"; action=function() timReform.Run = "NO" general.DoExecute(TblOption.Path) return iup.CLOSE end; }
local btnClosure = iup.button{ Font=iup_gui.FontBody; FgColor=iup_gui.Safe; MinSize="80"; Padding=iup_gui.Margin; Title=" Close\nMemo"; action=function() timReform.Run = "NO" return iup.CLOSE end; }
local boxButtons = iup.hbox { Homogeneous="YES"; Alignment="ACENTER"; btnFamHist; btnNotepad; btnExplore; btnClosure; }
local dialogMemo = iup.dialog{ Title=iup_gui.Plugin..iup_gui.Version.." Closing Memo"; iup.vbox { Alignment="ACENTER"; Gap="9"; Margin="4x4"; lblOption; lblExpNam; lblReform; lblChoose; lblBeware; lblSepLine; boxButtons; }; }
timReform.Run = "YES"
iup_gui.ShowDialogue("Memo",dialogMemo,btnClosure,"popup")
dialogMemo:destroy()
end -- function ClosingMessage
-- Export Gedcom File --
function ExportGedcom(doAnalyse,useRules,dicSour)
-- doAnalyse = Analyse function to save record names, _ROOT, Media _ASID/_SEQ, Witnessed Facts, etc
-- useRules = Rules function to convert records
SetSharedData() -- V4.0
general.MakeFolder(TblOption.Path,iup_gui.MemoDialogue) -- Ensure that the Export folder exists -- V2.9 -- V5.1
local tblMain = iup_gui.DialogueAttributes("Main") -- Obtain position of Main dialogue
local intMainX = tblMain.CoordX
local intMainY = tblMain.CoordY
local strFull = " "..TblMode[IntNode].Full -- Full product name of the chosen Mode of GEDCOM Export -- V5.0 IntNode
local strMode = " "..TblMode[IntMode].Abbr -- Abbreviated export mode "Std", "Min", "FTA", "TNG", etc -- V5.0 IntMode
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 strExpNam = StrFile..strMode..strCode..".ged" -- GEDCOM export file name & type -- V5.0
local strExport = TblOption.Path.."\\"..strExpNam -- GEDCOM export file full path -- V5.0
general.DeleteFile(strExport) -- Delete existing file so new file gets new Creation Date -- V5.0
local strExpAns,wasExpAns = general.FileNameToANSI(strExport,"Export Gedcom File Export.ged") -- V5.0
local putExport = general.OpenFile(strExpAns,"wb") -- V5.0
local strOption = strMode..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 strImpAns,wasImpAns = general.FileNameToANSI(StrImport,"Export Gedcom File Import.ged") -- V5.0
if not wasImpAns then doCopyFile(StrImport,strImpAns) end -- V5.0 -- V5.2
local intBytes = lfs.attributes(strImpAns,"size") -- V5.0
progbar.Setup( { X = intMainX+50; Y = intMainY+50; } ) -- Popup in same place as Main dialogue
progbar.Start("Analysing GEDCOM",intBytes) -- Progress Bar for analysis phase unconditionally -- V5.0
fhSleep(1000,400)
local isConflicts, arrConflict -- Tidy Place Record Name Conflicts signal, array, and Result Set
local arrPlace1 = {}
local arrRecId1 = {}
local arrPlace2 = {}
local arrRecId2 = {}
for strText,strTail,intLump in encoder.FileLines(strImpAns,"UTF-8") do -- V5.0 -- StrImport > strImpAns
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 Tidy Place Record Name Conflicts 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.."~ Tidy Place Record Name Conflicts")
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()
progbar.Setup( { X = intMainX+50; Y = intMainY+50; } ) -- Popup in same place as Main dialogue
progbar.Start("Exporting GEDCOM",intRecord) -- Progress Bar for GEDCOM export phase unconditionally -- V5.0
arrRecord = { } -- Clear arrays for first record
arrLineNo = { }
intLineNo = 0
for strText in encoder.FileLines(strImpAns,"UTF-8") do -- Slow 2nd Pass ~ Read each text line from FH GEDCOM import file -- V1.5 Unicode -- V5.0 StrImport > strImpAns
if not progbar.Stop() then -- Skip Exporting? -- V5.0 -- Allow loop to terminate otherwise general.DeleteFile(strImpAnsi) fails
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)
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
end
if dicSour.SOUR then fhDeleteItem(dicSour.SOUR) end -- Delete dummy Source record -- V5.5
if dicSour.SRCT then fhDeleteItem(dicSour.SRCT) end -- Delete dummy Source Template -- V5.5
fhUpdateDisplay()
--? 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
if not wasExpAns then general.MoveFile(strExpAns,strExport) end -- V5.0
if not wasImpAns then general.DeleteFile(strImpAns) end -- V5.0
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()
ClosingMessage(strOption,strExpNam,strExport) -- V5.6
if #ArrRule > 0 then -- Output Result Set arrays
if fhGetAppVersion() > 5 then
fhSetStringEncoding("UTF-8") -- V4.2 -- V5.7
end
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 messageSource:Export-Gedcom-File-27.fh_lua