Jump to content

Module:Str find word and Module:Str find word/sandbox: Difference between pages

(Difference between pages)
Page 1
Page 2
Content deleted Content added
Undid revision 1147915801 by Lemondoge (talk): oh dear. I checked with testcases - don't know how this goofed
 
No edit summary
 
Line 1: Line 1:
-- 2023-04-17 STABLE wrt basics, quotes "" '' * with base sep; working on resltstring & report
-- todo: report options, more options
-- todo: options count, pattern, out-table, out-htmllist, keepinputordersource
require('strict')
require('strict')
local p = {}
local p = {}
local getArgs = require('Module:Arguments').getArgs
local mArgs = require('Module:Arguments')
local str = require('Module:String')
local str = require('Module:String')
local yesno = require('Module:Yesno')
local yesno = require('Module:Yesno')
local defaultSep = ','
local tTools = require('Module:TableTools')
local strDeEnCode = require('Module:DecodeEncode')
local iMaxWords = 16
local iMaxWords = 12 -- alpha-status, Apr2023. when stable, can be higher
local warningIMaxWordsReached = nil
local xpLitWordCount = 0
local tArgs = {}
local report -- to be initinated when explain needed
local report = nil -- initinated when explain=T


local function parseReportType( tArgs )
-- Initialise the /report subpage.
local xpReportTF = false
-- only invoked when 'explain' asked
local xpReportType = yesno( tArgs.explain, tArgs.explain ) or false -- to be parsed beyond T/F
local function initReport()
-- in: nil, false: FALSE type=nil
-- in: true, preview: type=true TRUE (dflt: if prev)
-- in: doc, testcases: by page TRUE (persistent on those pages)
-- in: foo, other: FALSE

xpReportTF = false
if yesno( xpReportType, false ) == nil then -- nil, false
elseif xpReportType == 'testcases' then
xpReportType = 'testcases'
xpReportTF = true
elseif xpReportType == 'doc' then
xpReportType = 'doc'
xpReportTF = true
elseif xpReportType == true then
xpReportType = 'preview'
xpReportTF = true
else
xpReportTF = false -- unk word
end
tArgs.explain = xpReportTF
return xpReportType

end

local function initReport( tArgs )
report = require('Module:Str find word/report')
report = require('Module:Str find word/report')
report.xpCheckExplain() -- dummy
end

local function isPreview( ) -- here or in report?
local ifPreview = require('Module:If preview')
-- return not ( ifPreview._warning( {'is_preview'} ) == '' )
return ifPreview.main( true, false )
end
end


-- Turn "A" into "A" etc. asap
-- Turn "A" into "A" etc. asap
-- and reduce multi-spaces (including nbsp etc.) into single space
-- and reduce multi-spaces (including nbsp etc.) into single space
local function decodeUnicode(str)
local function sDecodeTrim( str )
if str == nil then return nil end
return mw.ustring.gsub(mw.text.decode(str), '%s+', ' ')
str = mw.ustring.gsub( strDeEnCode._decode( str ), '%s+' , ' ' )
return mw.text.trim( str )
end
end


-- %-Escape any word (character string) before feeding it into a string pattern function
-- %-Escape any word (character string) before feeding it into a string pattern function
-- will be %-escaped: "([%(%)%.%%%+%-%*%?%[%^%$%]])" = 12 characters ().%+-*?[^$]
-- all punctuation (%p) will be %-escaped
local function escape_word(word)
local function escape_word( word )
return str._escapePattern(word)
return str._escapePattern( word )
end

-- remove \' \" outer pair (& rm outer spaces);
-- any result (=the inner string) is trimmed by T/F option (case " abc ").
local function removeOuterQuotes( s, bTrimAfter )
if s == nil then return nil end

if mw.ustring.match( s, "^%s*\'" ) ~= nil then
s = mw.ustring.gsub( s, "^%s*%\'(.*)%\'%s*$", "%1" )
elseif mw.ustring.match( s, '^%s*\"' ) ~= '' then
s = mw.ustring.gsub( mw.text.trim( s ), '^%\"(.*)%\"$', '%1' )
end
if bTrimAfter == true then
s = mw.text.trim( s )
end
return s
end

-- separator-in
-- todo: check characters '" _ {}(); & accept?'
local function setSepIn( sSep, sDefaultSep )
if sSep == nil then return sDecodeTrim( sDefaultSep ) end
-- remove all %w (alphanumeric) and %s (WS)
sSep = mw.ustring.gsub( sDecodeTrim( sSep ), '[%w%s]*', '' ) or ''
if sSep == '' then
return sDecodeTrim( sDefaultSep )
else
return sSep
end
end

-- separator
local function setSepOut( sSep, sDefaultSep )
sSep = sDecodeTrim( sSep ) or nil
if sSep == nil then return sDefaultSep end
sSep = removeOuterQuotes( sSep, false )
if sSep == '' then
return sDefaultSep
else
return sSep
end
end

-- Check whether a single word is in a table (simple array of words)
-- returns hitword or nil; iPosition is helper to keep outlist ordered
local function findWordInTable( tSource, word )
---local bHit = false
---local iPosition = -1
for i, v in ipairs( tSource ) do
if v == word then
--- bHit = true --- del todo
---iPosition = i
return word
--- break
end
end

return nil
end
end


-- Reads and parses a word list and returns a table with words (simple array)
-- Reads and parses a word list and returns a table with words (simple array)
-- words list can be: source, andwords-to-check, orwords-to-check
-- words list can be: source, andwords-to-check, orwords-to-check
-- step 1: when case-insensitive, turn string into lowercase
-- step 1: basic preparation of the csv wordstring
-- step 2: read & remove Literals ("..")
-- step 2: when case-insensitive, turn string into lowercase
-- step 3: read comma-separated words
-- step 3: read (parse) quoted '..'
-- step 4: read (parse) quoted ".."
-- step 4: when booleans=T, change boolean words into true/false (module:yesno rules)
-- step 5: read (parse) comma-separated words
-- all words returned are trimmed, TODO and all ws into single-plainspace?
-- step 6: merge quoted wordlists; keep in order
-- only T/F words are edited, other words remain, untouched
-- step 7: when booleans=T, change boolean words into true/false (module:yesno rules)
-- step 8: replace synonyms (by inout "|_nov=November, 11" input)
-- step 9: remove duplicates from wordtable (rm latest)
-- all words returned are trimmed
-- return the table (a straight array)
-- return the table (a straight array)
local function buildWordTable(tArgs, sWordlist)
local function buildWordTable( sWordlist )
local wordTable = {}
local wordTable = {}
local hitWord = ''
local hitWord = ''
local hitCount = 0
local hitCount = -1
local _
if sWordlist == '' then return wordTable end
local sPattern
local cQ1 = '_Q0027_' -- U+0027 = \'
local cQ2 = '_Q0022_' -- U+0022 = \"
local tQ1hits = {} -- Q1-hits, reused to restore order
local tQ2hits = {} -- Q2-hits, reused to restore order
local sMsg = '' -- xpmessage only
local xpHasQuotes = false


-- Step 1: case-sensitive
-- Step 1: prepare sWordList
sDecodeTrim( sWordlist )
if yesno(tArgs.case, true) == false then
sWordlist = string.lower(sWordlist)
if sWordlist == '' or sWordlist == nil then return wordTable end
sWordlist = tArgs.sep .. sWordlist .. tArgs.sep
-- test. dev only:
xpHasQuotes = mw.ustring.match( sWordlist, '[\"\']' ) ~= '' -- unused
if xpHasQuotes then
--- report.xpMessage( 'xpHasQuotes [unused]: ' .. tostring( xpHasQuotes ) )
end
end


-- Step 2: read "literals",
-- Step 2: case sensitive
if yesno( tArgs.case, true ) == false then
-- then remove them from the string:
sWordlist = string.lower( sWordlist )
-- replaced by single comma; idle & keeps word separation
end
--- if yesno(tArgs.literals, false) then
if false then
local _, sCount
_, sCount = mw.ustring.gsub(sWordlist, '"', '')
if sCount > 1 then
local litWord = ''
local i, j


-- Step 3: Q1 read quotes (single quotes '..')
while sCount > 1 do -- could do here: only when even?
sPattern = '%f[^' .. tArgs.sep_pattern .. ']%s*%b\'\'%s*%f[' .. tArgs.sep_pattern .. ']'
i = string.find(sWordlist, '%"', 1, false)
-- initial:
j = string.find(sWordlist, '%"', i+1, false)
litWord = mw.text.trim(string.sub(sWordlist, i+1, j-1))
hitWord = sDecodeTrim( mw.ustring.match( sWordlist, sPattern ) ) or ''
while hitWord ~= '' do
if #litWord > 0 then -- not an empty string or spaces only
--- now into function/ to check if both \' and \" are not mixed
xpLitWordCount = xpLitWordCount + 1
--- hitWord = sDecodeTrim( mw.ustring.gsub( hitWord, "^%\'(.+)%\'$", "%1" ) ) -- remove outer Qs \"
table.insert(wordTable, litWord)
hitWord = removeOuterQuotes( hitWord, true )
end
table.insert( tQ1hits, hitWord )
-- remove from source, and do next gsub search:
sWordlist = string.gsub(sWordlist, '%"%s*'
sWordlist = mw.ustring.gsub( sWordlist, sPattern, cQ1, 1 ) -- removes current 1st hit; replace with code

.. escape_word(litWord)
-- next
.. '%s*%"', ',')
_, sCount = mw.ustring.gsub(sWordlist, '"', '')
hitWord = sDecodeTrim( mw.ustring.match( sWordlist, sPattern ) ) or ''
end
end
end
end
--- report.xpMessage( 'sWL1: ' .. sWordlist )
--- report.xpMessage( 'Qhits: ' .. table.concat( tQ1hits, '; ' ) )
-- Step 3: parse comma-delimited words
-- Step 4: Q2 read quotes (double quotes "..")
sPattern = '%f[^' .. tArgs.sep_pattern .. ']%s*%b\"\"%s*%f[' .. tArgs.sep_pattern .. ']'
hitCount = 0
-- initial search
sWordlist = tArgs.sep .. sWordlist .. tArgs.sep
hitWord = sDecodeTrim( mw.ustring.match( sWordlist, sPattern ) ) or ''
local eSep
while hitWord ~= '' do
eSep = escape_word(tArgs.sep)
--- hitWord = sDecodeTrim( mw.ustring.gsub( hitWord, '^%\"(.+)%\"$', '%1' ) ) -- remove outer Qs \"
local patstring = '%f[^' .. eSep .. '][^' .. eSep .. ']+%f[' .. eSep .. ']'
hitWord = removeOuterQuotes( hitWord, true )
if yesno(tArgs.explain, false) then
table.insert( tQ2hits, hitWord )
report.xpMessage('1.eSep: ' .. eSep) -- dev
sWordlist = mw.ustring.gsub( sWordlist, sPattern, cQ2, 1 ) -- removes current '1st' hit; replace with code
report.xpMessage('2.pattern: ' .. patstring) -- dev
-- next
hitWord = sDecodeTrim( mw.ustring.match( sWordlist, sPattern ) ) or ''
end
end
---report.xpMessage( 'sWL2:' .. sWordlist )
while hitCount <= iMaxWords do
---report.xpMessage( 'Qhits: ' .. table.concat( tQ2hits, '; ' ) )
hitCount = hitCount + 1

-- Step 5: parse plain sep-delimited words
sPattern = '%f[^' .. tArgs.sep_pattern .. '][^' .. tArgs.sep_pattern .. ']+%f[' .. tArgs.sep_pattern .. ']'
hitCount = 0
while hitCount < iMaxWords do
hitWord = sDecodeTrim( str._match( sWordlist, sPattern, 1, hitCount + 1, false, tArgs.sep ) ) or ''
hitWord = str._match(sWordlist, patstring, 1, hitCount, false, tArgs.sep)
if hitWord == sDecodeTrim(tArgs.sep) then
hitWord = mw.text.trim(hitWord)
if hitWord == tArgs.sep then
-- no more words found in the string
-- no more words found in the string
break
break
elseif hitWord ~= '' then
elseif hitWord ~= '' then
hitCount = hitCount + 1
table.insert(wordTable, hitWord)
table.insert( wordTable, hitWord )
else -- blank word, to skip (note: but blank quotes as in .., " ", ..are kept = blank dcell '')
hitCount = hitCount + 1
end
end
end
end
if hitCount > iMaxWords then
if hitCount >= iMaxWords then report.xpMessage( 'ERR701 wordcount ' .. hitCount .. ' > maxwords' .. iMaxWords ) end

warningIMaxWordsReached = 'Max number of words (' .. tostring(iMaxWords) .. ') reached. Extra words are ignored.'
-- Step 6: merge quoted words & wordtable, keep order
.. ' (' .. mw.ustring.sub(mw.text.trim(sWordlist), 1, 90) .. '&nbsp;...). '
for iQ, sQW in ipairs( tQ1hits ) do
end
for iW, sW in ipairs( wordTable ) do
if sW == cQ1 then
wordTable[iW] = sQW
break
end
end
end
for iQ, sQW in ipairs( tQ2hits ) do
for iW, sW in ipairs( wordTable ) do
if sW == cQ2 then
wordTable[iW] = sQW
break
end
end
end


-- Step 4: when read booleans, converse words to true/false
-- Step 7: when read as booleans, converse words to true/false
if tArgs.booleans then
-- todo: check parameter here not elsewhere
if tArgs.booleans then -- TODO if Yesno(tArgs.booleans) ...
local sBool
local sBool
for i, v in ipairs(wordTable) do
for i, v in ipairs( wordTable ) do
sBool = yesno(v)
sBool = yesno( v )
if sBool ~= nil then
if sBool ~= nil then
wordTable[i] = tostring(sBool)
wordTable[i] = tostring( sBool )
end
end
end
end
end
end


-- Step 8: replace synonyms
return wordTable
if #tArgs['synonymsTables'] >= 1 then
end
for aka1, tAkas in pairs ( tArgs['synonymsTables'] ) do
for iW, w in ipairs( wordTable ) do
if findWordInTable( tAkas, w ) then -- todo must be ... ~= nil ??? 26-3
wordTable[iW] = aka1
end
end
end
end


if true then
-- Check whether a single word is in a table (a simple array of words)
wordTable = tTools.removeDuplicates( wordTable )
-- returns hitword or nil
else -- lol works but not needed, use ttools
local function findWordInTable(sourceWordTable, word)
-- Step 9: remove duplicates from list
local bHit = false
local iR, iK -- iR = reader, iK = killer
for i, v in ipairs(sourceWordTable) do
local hit = false
if v == word then
bHit = true
iR = 1
while iR < #wordTable do
break
iK = #wordTable -- will be counting downwards
while iK > iR do
if wordTable[iK] == wordTable[iR] then
hit = true
sMsg = sMsg .. '=syn=' .. wordTable[iK]
table.remove( wordTable, iK )
tTools.compressSparseArray( wordTable )
end
iK = iK - 1
end
end
tTools.compressSparseArray( wordTable )
iR = iR + 1
end
end
end
if bHit then

return word
return wordTable
else
return nil
end
end
end


-- AND-logic with andWordTable words: ALL words must be found
-- AND-logic with ANDwords words: ALL words must be found
-- returns {T/F, hittable}
-- returns {T/F, hittable}
-- T when *all* AND words are found
-- T when *all* AND words are found
-- hittable with all hit words
-- hittable with all hit words
-- note 1: when F, the hittable still contains the words that were found
-- note 1: when F, the hittable still contains the words that were found
-- note 2: empty AND-wordlist => True by logic (because: not falsified)
-- note 2: empty AND-wordlist => True by logic (because: not falsified)
local function checkANDwords(sourceWordTable, andWordTable)
local function checkANDwords( tWorkf )
local bANDchk = true -- main conclusion
local result1
local result1 = nil -- per word hit
local bAND
local tHits
local tHits = {} -- hit table
---local iPos = -1 -- helper info just to keep in order


if #tWorkf.ANDwords > 0 then
bAND = true
bANDchk = true
tHits = {}
for i, word in ipairs( tWorkf.ANDwords ) do
result1 = nil
result1 = findWordInTable( tWorkf.SOURCEwords, word ) or nil
if #andWordTable > 0 then
for i, word in ipairs(andWordTable) do
result1 = findWordInTable(sourceWordTable, word) or nil
if result1 == nil then
if result1 == nil then
bAND = false -- Falsified!
bANDchk = false -- Falsified!
-- could break after this logically but
-- We could break now logically, but we continue to complete the hit table (feature)
-- continue to complete the table (bAND remains false)
-- bAND remains false till & at end of loop
else
else
table.insert(tHits, result1)
table.insert( tHits, result1 )
end
end
end
end
else
else
bAND = true
bANDchk = true -- not falsified
end
end
tTools.compressSparseArray( tHits )

return bAND, tHits
return bANDchk, tHits
end
end


-- OR-logic with orWordTable words: at least one word must be found
-- OR-logic with tORwords words: at least one word must be found
-- returns {T/F, hittable}
-- returns {T/F, hittable}
-- True when at least one OR word is found
-- True when at least one OR word is found
-- hittable has all hit words
-- hittable has all hit words
-- note 1: empty OR-wordlist => True by logic (because: not falsified)
-- note 1: empty OR-wordlist => True by logic (because: not falsified)
-- note 2: while just one hitword is a True result, the hittable contains all words found
-- note 2: while just one hitword is a True result, the hittable contains all words found
local function checkORwords(sourceWordTable, orWordTable)
local function checkORwords( tWork )
local result1
local result1
local bOR
local bORchk
local tHits
local tHits


bOR = false
bORchk = false
tHits = {}
tHits = {}
result1 = nil
result1 = nil
if #orWordTable > 0 then
if #tWork.ORwords > 0 then
for i, word in ipairs(orWordTable) do
for i, word in ipairs( tWork.ORwords ) do
result1 = findWordInTable(sourceWordTable, word) or nil
result1 = findWordInTable( tWork.SOURCEwords, word ) or nil
if result1 == nil then
if result1 == nil then
-- this one is false; bOR unchanged; do next
-- this one is false; bOR unchanged; do next
else
else
bOR = true -- Confirmed!
bORchk = true -- Confirmed!
table.insert(tHits, result1)
table.insert( tHits, result1 )
-- could break here logically, but complete the check
-- could break here logically, but complete the check; bOR will not be set to False
end
end
end
end
else
else
bOR = true
bORchk = true
end
end
tTools.compressSparseArray( tHits )

return bOR, tHits
return bORchk, tHits
end
end


-- Determine the requested return value (string).
-- Determine the requested return value (a string)
-- sYeslist is the _main return value (logically defined value)
-- sRESULTstring is the _main return value (logically defined value)
-- this function applies tArgs.yes / tArgs.no return value
-- this function applies tArgs.out_true / tArgs.out_false return value
-- note: yes='' implies: blank return value
-- note: out_true='' implies: blank return value
-- note: no parameter yes= (that is, yes=nil) implies: by default, return the sYeslist
-- note: no parameter out_true= (that is, out_true=nil) implies: by default, return the sRESULTstring
--- todo add pref, suff
local function yesnoReturnstring(tArgs, sYeslist)
local function yesnoReturnstring( tResults )
if sYeslist == '' then -- False
if tResults.resultALL == false then -- result False
return tArgs.no or ''
return tArgs.out_false or ''
else -- True
else -- result True
if tArgs.yes == nil then
if tArgs.out_true == nil then
return sYeslist
return table.concat( tResults.tTRUE, tArgs.out_sep )
else -- some |yes= value is entered, could be ''
else -- some |out-true= value is entered, could be ''
return tArgs.yes
return '_out-true' .. tArgs.out_true
end
end
end
end
end
end


local function isPreview()
local function tCombinedSourceorderedTRUEtables( tResult )
local tOut = {}
local ifPreview = require('Module:If preview')
if tResult.tANDhits == nil then
return not (ifPreview._warning( {'is_preview'} ) == '')
tOut = tResult.tORhits
elseif tResult.tORhits == nil then
tOut = tResult.tANDhits
else
tOut = tResult.tANDhits
for i, v in ipairs( tResult.tORhits ) do
table.insert( tOut, i, v )
end
end
if tOut == nil then
report.xpMessage( 'ERR921 BUG tOut is nil??? - tCombinedSourceorderedTRUEtables' )
end
return tOut -- unsorted; never nil
end
end


local function concatAndLists( s1, s2 )
-- Explain options (=report info), interprets parameter explain=
local tLists = {} -- args in: both s1 and s2 to concat
-- returns true/false/'testcases'
table.insert( tLists, s1 )
-- explain=true => show report in Preview
table.insert( tLists, s2 )
-- explain=testcases => WHEN in ns: template: or user: AND subpage = '/testcases' THEN show permanently
return table.concat( tLists, tArgs.sep )
local function checkExplain(tArgs)
return false -- never. 22Mar2023 checkExplain(newArgs)
end
end


-- ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
-- ===== ===== ===== ===== ===== ===== ===== ===== =====
-- PARSE arguments
-- _main function: check for presence of words in source string
local function parseArgs( origArgs )
-- Checks and returns:
local tNewArgs = {}
-- when T: the string of all hitwords (default), or the |yes=... input
local tDefault = {}
-- when F: empty string '' (default), or the |no=... input
tDefault['sep'] = ','
-- steps:
tDefault['case'] = false
-- 1. input word strings are prepared (parsed into an array of words)
tDefault['booleans'] = false
-- 2. words checks are made (applying AND-logic, OR-logic)
tDefault['out_sep'] = ', '
-- 3. final conclusion drawn (T/F)
-- 4. optionally, the preview report is prepared (debug, feedback)
-- 5. based on T or F status, the return value (string) is established and returned
-- note 1: each return value (yes=.., no=..) can be '' (nulstring)
function p._main(tArgs)
local sourceWordTable = {}
local andWordTable = {}
local orWordTable = {}
local tANDhits
local tORhits
-- logical finding:
local bANDresult = false
local bORresult = false
local resultALL = false
local sYeslist = ''


tNewArgs.sep = setSepIn( origArgs['sep'], tDefault['sep'] )
sourceWordTable = buildWordTable(tArgs, tArgs.source)
tNewArgs.sep_pattern = escape_word( tNewArgs.sep )
andWordTable = buildWordTable(tArgs, tArgs.andString)
tNewArgs.out_sep = setSepOut( origArgs['out-sep'] or origArgs['sep'], tDefault['out_sep'] )
orWordTable = buildWordTable(tArgs, tArgs.orString)
tNewArgs.case = yesno( origArgs['case'] or origArgs['casesensitive'] ) or tDefault['case']
tNewArgs.booleans = yesno( origArgs['bool'] or origArgs['booleans'] ) or tDefault['booleans']
tNewArgs.out_true = sDecodeTrim( origArgs.out_true ) or nil -- nil =default so return sRESULTstring; keep '' as legal input & return value
tNewArgs.out_false = sDecodeTrim( origArgs.out_false ) or ''
tNewArgs.prefix = sDecodeTrim( origArgs.prefix or origArgs.p ) or ''
tNewArgs.suffix = sDecodeTrim( origArgs.suffix or origArgs.s ) or ''
tNewArgs.out_format = 'default' -- todo: table, default, htmllisttype, flatlidt , first,
tNewArgs.explain = false -- TEST17Apr origArgs.explain
tNewArgs.explain_type = parseReportType( tNewArgs ) or nil
tNewArgs.test = origArgs.test


-- the wordlists:
if (#sourceWordTable == 0) or (#andWordTable + #orWordTable == 0) then
tNewArgs['source'] = origArgs['source'] or origArgs['s'] or ''
-- No words to check
tNewArgs['sANDlist'] = concatAndLists(
resultALL = false
origArgs['word'] or origArgs['w'] or nil,
if yesno(tArgs.explain, false) then
origArgs['andwords'] or origArgs['andw'] or nil )
report.xpNoWords(tArgs, sourceWordTable, andWordTable, orWordTable)
tNewArgs['sORlist'] = origArgs['orwords'] or origArgs['orw'] or ''

tNewArgs['synonyms'] = {}
tNewArgs['synonymsTables'] = {} -- to be populated later
for k, v in pairs( origArgs ) do
if str._match( k, '^_%S', 1, 1, false, false ) then
local syn1
syn1 = mw.ustring.gsub( k, '^_', '', 1 )
table.insert( tNewArgs['synonyms'], syn1 )
tNewArgs['synonyms'][syn1] = v
end
end
else
bANDresult, tANDhits = checkANDwords(sourceWordTable, andWordTable)
bORresult, tORhits = checkORwords(sourceWordTable, orWordTable)
resultALL = (bANDresult) and (bORresult)
end
end


if tNewArgs.explain == true then
sYeslist = ''
initReport( tNewArgs.explain )
if resultALL then
report.xpMessage( 'EXPLAIN: ' .. origArgs.explain .. '=>' .. tNewArgs.explain_type or 'unk')
-- concat the sYeslist (= all hit words; from 2 tables)
report.xpReportSynonyms( tNewArgs )
if bANDresult then
sYeslist = sYeslist .. table.concat(tANDhits, tArgs.sep)
end

if #tORhits > 0 then
if #tANDhits > 0 then
sYeslist = sYeslist .. tArgs.sep
end
sYeslist = sYeslist .. table.concat(tORhits, tArgs.sep)
end
end
end
if yesno(tArgs.explain, false) then
if tArgs.yes ~= nil then
if (tArgs.yes == '') and (tArgs.no == '') then
report.xpYesNoBothBlank()
end
end
if warningIMaxWordsReached ~= nil then
report.xpMessage(warningIMaxWordsReached)
end
report.xpBuildReport(tArgs, sourceWordTable,
bANDresult, andWordTable, tANDhits,
bORresult, orWordTable, tORhits,
sYeslist, xpLitWordCount)
end
return yesnoReturnstring(tArgs, sYeslist)
end


if false then
-- set wordt separator
for aka1, sAkalist in pairs ( tNewArgs['synonyms'] ) do
local function setSep(sSep)
report.xpMessage( 'SYNONYMS: ' .. aka1 .. '=' .. sAkalist )
if sSep == nil then return defaultSep end
local msg = ''
-- todo what with {{!}}
local newSep = defaultSep

newSep = sSep
sSep = decodeUnicode(sSep)
if string.match(sSep, '[%s%w%d]') ~= nil then -- not ok
msg = 'Irregular characters in sep: ' .. sSep
newSep = defaultSep
end
newSep = string.sub(sSep, 1, 1)
if newSep == '' then --- ???
newSep = defaultSep
end
end
return newSep
end
end


return tNewArgs
local function concatAndLists(s1, s2, newSep)
local tLists = {} -- working table: both s1 and s2 to concat
table.insert(tLists, s1)
table.insert(tLists, s2)
return table.concat(tLists, newSep)
end
end


-- ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
local function parseArgs(origArgs)
-- _main function: check for presence of words in source string
local newArgs = {}
-- Checks and returns:
newArgs['sep'] = setSep(origArgs['sep']) -- do first, needed below
-- when T: the string of all hitwords ( default ), or the |yes=... input
newArgs['source'] = decodeUnicode(origArgs['s'] or origArgs['source'] or '')
-- when F: empty string '' ( default ), or the |no=... input
newArgs['andString'] = decodeUnicode(concatAndLists(
-- steps:
origArgs['w'] or origArgs['word'] or nil,
-- 1. input word strings are prepared ( parsed into an array of words )
origArgs['andw'] or origArgs['andwords'] or nil,
-- 2. words checks are made ( applying AND-logic, OR-logic )
newArgs.sSep)
-- 3. final conclusion drawn ( T/F )
)
-- 4. optionally, the preview report is prepared ( debug, feedback )
newArgs['orString'] = decodeUnicode(origArgs['orw'] or origArgs['orwords'] or '')
-- 5. based on T or F status, the return value ( string ) is established and returned
-- boolean options: catch both parameters, also handle nil & nonsense input values:
-- note 1: each return value ( yes=.., no=.. ) can be '' ( nullstring )
newArgs['case'] = yesno(origArgs['case'] or origArgs['casesensitive'] or true, true) -- defaults to True
function p._main( origArgs )
newArgs['booleans'] = yesno(origArgs['bool'] or origArgs['booleans'] or false, false) -- defaults to False
local tWork = {}
newArgs['literals'] = yesno(origArgs['literals'] or origArgs['lit'] or true, true) -- defaults to True
local tResults = {}
newArgs['yes'] = origArgs['yes'] or nil -- nil; default so return sYeslist; keep '' as legal input & return value
newArgs['no'] = origArgs['no'] or ''
newArgs['explain'] = false -- never. 22Mar2023 checkExplain(newArgs)


tArgs = parseArgs( origArgs )
newArgs.explain = false -- never. 22Mar2023 checkExplain(newArgs)
-- make synonyms into tables
return newArgs
-- 'aka1' = target synonym (= the synonym that remains)
end
for aka1, sAkalist in pairs( tArgs['synonyms'] ) do
tArgs['synonymsTables'][aka1] = buildWordTable( tArgs['synonyms'][aka1] )
end


-- build the worktables
function p.main(frame)
tWork['SOURCEwords'] = buildWordTable( tArgs.source )
local origArgs = getArgs(frame)
tWork['ANDwords'] = buildWordTable( tArgs.sANDlist )
local sReturn = ''
tWork['ORwords'] = buildWordTable( tArgs.sORlist )
local tArgs = {}


-- apply logic & conclude
tArgs = parseArgs(origArgs)
tResults.resultALL = nil -- best be set explicitly
if yesno(tArgs.explain, false) then
if ( #tWork.SOURCEwords == 0 ) or ( #tWork.ANDwords + #tWork.ORwords == 0 ) then
initReport()
-- No words to check
report.xpListArguments(origArgs)
tResults.resultALL = false
if yesno( tArgs.explain, true ) then
report.xpMessage( 'ERR201 No words to check' )
end
else
tResults['bAND'], tResults['tANDhits'] = checkANDwords( tWork )
tResults['bOR'], tResults['tORhits'] = checkORwords( tWork )
tResults.resultALL = ( tResults.bAND ) and ( tResults.bOR )
end
end


tResults.sRESULTstring = 'notinit'
sReturn = p._main(tArgs)
if tResults.resultALL == true then
tResults.tTRUE = tCombinedSourceorderedTRUEtables( tResults ) or {}
if warningIMaxWordsReached ~=nil then
local preview = require('Module:If preview')
sReturn = sReturn .. preview._warning({warningIMaxWordsReached})
end
end
tResults.sRESULTstring = yesnoReturnstring( tResults )


local sReport = ''
if yesno(tArgs.explain, false) then
return sReturn .. report.xpPresent(tArgs.explain)
if tArgs.explain then
sReport = 'xp endfinal Report here L485'
else
--sReport = report.xpPresent( tArgs, tWork, tResults )
return sReturn
end
end
local test = 'Tunk'
test = tArgs.test or '_unk'

if tArgs.explain then
test = tostring(tArgs.explain)
else
test = 'not'
end
return string.upper( tostring( tResults.resultALL ) ) .. tResults.sRESULTstring
end

function p.main( frame )
local origArgs = mArgs.getArgs( frame )

return p._main( origArgs )
end
end