Search and Return Result Set.fh_lua--[[
@title: Search and Return Result Set
@author: Calico Pie
@lastupdated: 4 SEP 2023
@version: 1.8
@Licence: This plugin is copyright (c) 2023 and 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:
Search for a specified word or phrase in all text fields in the current project, with options to narrow the scope of the search.
The matching fields are listed in a table in the Query Window result set,
with links back to each context of use (double-click on any Item to view it in the Property Box).
1.8 Handle Text Meta-fields as Text
1.7 Add support for Attribute values
1.6 Correct Rich-text Element support
1.5 V7 Compatibility
1.4 Use prototype functions and fix issue with case sensitive searching.
1.3 Added Whole Word option and improve the message prompting.
1.2 Added Case insensitive searching.
]]
-------------
-- Main Code
-------------
require "iuplua"
iup.SetGlobal("CUSTOMQUITMESSAGE","YES")
function mainfunction()
local tblRecord = {}
local tblItem = {}
local tblRecordType = {}
local tblItemDataRef = {}
local iCount = 0
-- Prompt for Options
-- Prompt for confirmation using iup.GetParm %b are tick boxes
-- set initial values
local pa ={text=1,longtext=1,name=1,place=1,wordlist=1}
local pConfirm = 1
local pSensitive = 0
local pWhole = 1
local pPlain = 1
local bOK,strSearch
local ptrRecord = fhNewItemPtr()
-- Prompt User to Confirm Options
bOK, strSearch,pSensitive,pa.text,pa.longtext,pa.name,pa.place,pPlain,pWhole =
iup.GetParam("Search and Return Result Set", param_action,
"Search For: %s\n"..
"Case Sensitive: %b\n"..
"Search in the following Classes %t\n"..
"Text fields: %b{Name Prefix/Suffix/Given/Nickname, Attribute values, Cause, Where Within Source, Title, Type, Custom Id, City, State, Postcode, Country, Phone, E-mail, Website, File, Format, Keywords, Sentence, etc.}\n"..
"Long Text: %b{Note, Address, Author, Text From Source, Publication Info, Link/Note}\n"..
"Names: %b{Name fields only}\n"..
"Places: %b{Place fields only}\n"..
"Extra Search Options %t\n"..
"Plain Text Search: %b{Plain Text or Regular Expression search & replace.}\n"..
"Whole Words (needs plain text): %b{Whole words are delimited by spaces, tabs, and punctuation.}\n"
,
"",pSensitive,pa.text,pa.longtext,pa.name,pa.place,pPlain,pWhole)
-- bOK means OK was pressed so do search
if bOK then
-- Set "odd" field classes as per text
pa.word = pa.text
pa.wordlist = pa.text
-- Set up new search function
local search = newSearch(strSearch,pWhole,pPlain,pSensitive)
-- Swap 1 for true
for strType,Value in pairs(pa) do
pa[strType] = (Value == 1)
end
-- Process All Items in Data
for ptr in allItems() do
local strDataClass = fhGetDataClass(ptr)
if strDataClass == 'richtext' then strDataClass = 'text' end
if strDataClass == 'metafield' then strDataClass = 'text' end
sType = fhGetValueType(ptr)
print(strDataClass, sType,fhGetValueAsText(ptr))
if (sType == 'text' or sType == 'richtext' ) and pa[strDataClass] == true then
local strFromString = fhGetValueAsText(ptr)
if strDataClass == 'name' then
strFromString = strFromString:gsub('/',' ') -- Special Case remove // around surnames
end
if search(strFromString) then
iCount = iCount + 1
ptrRecord:MoveToRecordItem(ptr)
tblItem[iCount]= ptr:Clone()
tblRecord[iCount]= ptrRecord:Clone()
tblRecordType[iCount]= fhGetTag(ptrRecord)
tblItemDataRef[iCount]= BuildDR(ptr)
end
end
end
if iCount > 0 then
local title = 'Search for '..strSearch..' in '..fhGetContextInfo('CI_PROJECT_NAME')
fhOutputResultSetTitles(title, title, "Date: %#x")
fhOutputResultSetColumn("Item", "item", tblItem, #tblItem, 180, "align_left")
fhOutputResultSetColumn("Record", "item", tblRecord, #tblRecord, 180, "align_left")
fhOutputResultSetColumn("Record Type", "text", tblRecordType, #tblRecordType, 40, "align_left")
fhOutputResultSetColumn("Item Data Reference", "text", tblItemDataRef, #tblItemDataRef, 140, "align_left")
else
fhMessageBox(strSearch..' Not Found')
end
else
return
end
end
function newSearch(strSearch,pmWhole,pmPlain,pmSensitive)
local function escape(s)
return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1'):gsub('%z','%%z'))
end
local function nocase (s)
s = string.gsub(s, "%a", function (c)
return string.format("[%s%s]", string.lower(c),
string.upper(c))
end)
return s
end
local iPos,iPos2,iPos3
local strSearchFor = strSearch
local strSearchForEnd, strSearchForStart
if pmPlain == 1 then
strSearchFor = escape(strSearchFor)
end
if pmSensitive ~= 1 then
strSearchFor = nocase(strSearchFor)
end
if pmWhole == 1 and pmPlain == 1 then
strSearchForEnd = '[%s%p]'..strSearchFor..'$' -- End of Line
strSearchForStart = '^'..strSearchFor..'[%s%p]' -- Start of Line
strSearchFor = '[%s%p]'..strSearchFor..'[%s%p]' -- Delimited by Space or Punctuation
end
--- Return Function for use in data traversal.
return
function (strFromString)
iPos = string.find(strFromString,strSearchFor)
if pmWhole == 1 and pmPlain == 1 then
-- Special Case search for last word and first word
iPos2 = string.find(strFromString,strSearchForEnd)
iPos3 = string.find(strFromString,strSearchForStart)
end
return (iPos or iPos2 or iPos3)
end
end
function BuildDR(ptr)
local ptrTemp = fhNewItemPtr()
ptrTemp:MoveTo(ptr)
strDR = fhGetTag(ptrTemp)
while fhHasParentItem(ptrTemp) do
ptrTemp:MoveToParentItem(ptrTemp)
strDR = fhGetTag(ptrTemp)..'.'..strDR
end
return strDR
end
function allItems(...)
arg = {...}
arg['n'] = #arg
local iTypeCount = nil
local iPos = 1
local p1 = fhNewItemPtr()
local p2 = fhNewItemPtr()
local tblRecTypes = {}
if arg['n'] == 0 then
-- No parameter do all Record Types
iTypeCount = fhGetRecordTypeCount() -- Get Count of Record types
for i = 1,iTypeCount do
tblRecTypes[i] = fhGetRecordTypeTag(i)
end
else
-- Got Parameters Process them instead
tblRecTypes = arg
iTypeCount = arg['n']
end
p1:MoveToFirstRecord(tblRecTypes[iPos])
return function()
repeat
while p1:IsNotNull() do
p2:MoveTo(p1)
p1:MoveNextSpecial()
if p2:IsNotNull() then
return p2
end
end
-- Loop through Record Types
iPos = iPos + 1
if iPos <= iTypeCount then
p1:MoveToFirstRecord(tblRecTypes[iPos])
end
until iPos > iTypeCount
end
end
-- Call Main
mainfunction()
--[[
@title: Search and Return Result Set
@author: Calico Pie
@lastupdated: 4 SEP 2023
@version: 1.8
@Licence: This plugin is copyright (c) 2023 and 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:
Search for a specified word or phrase in all text fields in the current project, with options to narrow the scope of the search.
The matching fields are listed in a table in the Query Window result set,
with links back to each context of use (double-click on any Item to view it in the Property Box).
1.8 Handle Text Meta-fields as Text
1.7 Add support for Attribute values
1.6 Correct Rich-text Element support
1.5 V7 Compatibility
1.4 Use prototype functions and fix issue with case sensitive searching.
1.3 Added Whole Word option and improve the message prompting.
1.2 Added Case insensitive searching.
]]
-------------
-- Main Code
-------------
require "iuplua"
iup.SetGlobal("CUSTOMQUITMESSAGE","YES")
function mainfunction()
local tblRecord = {}
local tblItem = {}
local tblRecordType = {}
local tblItemDataRef = {}
local iCount = 0
-- Prompt for Options
-- Prompt for confirmation using iup.GetParm %b are tick boxes
-- set initial values
local pa ={text=1,longtext=1,name=1,place=1,wordlist=1}
local pConfirm = 1
local pSensitive = 0
local pWhole = 1
local pPlain = 1
local bOK,strSearch
local ptrRecord = fhNewItemPtr()
-- Prompt User to Confirm Options
bOK, strSearch,pSensitive,pa.text,pa.longtext,pa.name,pa.place,pPlain,pWhole =
iup.GetParam("Search and Return Result Set", param_action,
"Search For: %s\n"..
"Case Sensitive: %b\n"..
"Search in the following Classes %t\n"..
"Text fields: %b{Name Prefix/Suffix/Given/Nickname, Attribute values, Cause, Where Within Source, Title, Type, Custom Id, City, State, Postcode, Country, Phone, E-mail, Website, File, Format, Keywords, Sentence, etc.}\n"..
"Long Text: %b{Note, Address, Author, Text From Source, Publication Info, Link/Note}\n"..
"Names: %b{Name fields only}\n"..
"Places: %b{Place fields only}\n"..
"Extra Search Options %t\n"..
"Plain Text Search: %b{Plain Text or Regular Expression search & replace.}\n"..
"Whole Words (needs plain text): %b{Whole words are delimited by spaces, tabs, and punctuation.}\n"
,
"",pSensitive,pa.text,pa.longtext,pa.name,pa.place,pPlain,pWhole)
-- bOK means OK was pressed so do search
if bOK then
-- Set "odd" field classes as per text
pa.word = pa.text
pa.wordlist = pa.text
-- Set up new search function
local search = newSearch(strSearch,pWhole,pPlain,pSensitive)
-- Swap 1 for true
for strType,Value in pairs(pa) do
pa[strType] = (Value == 1)
end
-- Process All Items in Data
for ptr in allItems() do
local strDataClass = fhGetDataClass(ptr)
if strDataClass == 'richtext' then strDataClass = 'text' end
if strDataClass == 'metafield' then strDataClass = 'text' end
sType = fhGetValueType(ptr)
print(strDataClass, sType,fhGetValueAsText(ptr))
if (sType == 'text' or sType == 'richtext' ) and pa[strDataClass] == true then
local strFromString = fhGetValueAsText(ptr)
if strDataClass == 'name' then
strFromString = strFromString:gsub('/',' ') -- Special Case remove // around surnames
end
if search(strFromString) then
iCount = iCount + 1
ptrRecord:MoveToRecordItem(ptr)
tblItem[iCount]= ptr:Clone()
tblRecord[iCount]= ptrRecord:Clone()
tblRecordType[iCount]= fhGetTag(ptrRecord)
tblItemDataRef[iCount]= BuildDR(ptr)
end
end
end
if iCount > 0 then
local title = 'Search for '..strSearch..' in '..fhGetContextInfo('CI_PROJECT_NAME')
fhOutputResultSetTitles(title, title, "Date: %#x")
fhOutputResultSetColumn("Item", "item", tblItem, #tblItem, 180, "align_left")
fhOutputResultSetColumn("Record", "item", tblRecord, #tblRecord, 180, "align_left")
fhOutputResultSetColumn("Record Type", "text", tblRecordType, #tblRecordType, 40, "align_left")
fhOutputResultSetColumn("Item Data Reference", "text", tblItemDataRef, #tblItemDataRef, 140, "align_left")
else
fhMessageBox(strSearch..' Not Found')
end
else
return
end
end
function newSearch(strSearch,pmWhole,pmPlain,pmSensitive)
local function escape(s)
return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1'):gsub('%z','%%z'))
end
local function nocase (s)
s = string.gsub(s, "%a", function (c)
return string.format("[%s%s]", string.lower(c),
string.upper(c))
end)
return s
end
local iPos,iPos2,iPos3
local strSearchFor = strSearch
local strSearchForEnd, strSearchForStart
if pmPlain == 1 then
strSearchFor = escape(strSearchFor)
end
if pmSensitive ~= 1 then
strSearchFor = nocase(strSearchFor)
end
if pmWhole == 1 and pmPlain == 1 then
strSearchForEnd = '[%s%p]'..strSearchFor..'$' -- End of Line
strSearchForStart = '^'..strSearchFor..'[%s%p]' -- Start of Line
strSearchFor = '[%s%p]'..strSearchFor..'[%s%p]' -- Delimited by Space or Punctuation
end
--- Return Function for use in data traversal.
return
function (strFromString)
iPos = string.find(strFromString,strSearchFor)
if pmWhole == 1 and pmPlain == 1 then
-- Special Case search for last word and first word
iPos2 = string.find(strFromString,strSearchForEnd)
iPos3 = string.find(strFromString,strSearchForStart)
end
return (iPos or iPos2 or iPos3)
end
end
function BuildDR(ptr)
local ptrTemp = fhNewItemPtr()
ptrTemp:MoveTo(ptr)
strDR = fhGetTag(ptrTemp)
while fhHasParentItem(ptrTemp) do
ptrTemp:MoveToParentItem(ptrTemp)
strDR = fhGetTag(ptrTemp)..'.'..strDR
end
return strDR
end
function allItems(...)
arg = {...}
arg['n'] = #arg
local iTypeCount = nil
local iPos = 1
local p1 = fhNewItemPtr()
local p2 = fhNewItemPtr()
local tblRecTypes = {}
if arg['n'] == 0 then
-- No parameter do all Record Types
iTypeCount = fhGetRecordTypeCount() -- Get Count of Record types
for i = 1,iTypeCount do
tblRecTypes[i] = fhGetRecordTypeTag(i)
end
else
-- Got Parameters Process them instead
tblRecTypes = arg
iTypeCount = arg['n']
end
p1:MoveToFirstRecord(tblRecTypes[iPos])
return function()
repeat
while p1:IsNotNull() do
p2:MoveTo(p1)
p1:MoveNextSpecial()
if p2:IsNotNull() then
return p2
end
end
-- Loop through Record Types
iPos = iPos + 1
if iPos <= iTypeCount then
p1:MoveToFirstRecord(tblRecTypes[iPos])
end
until iPos > iTypeCount
end
end
-- Call Main
mainfunction()Source:Search-and-Return-Result-Set-4.fh_lua