require('strict')local p = local getArgs = require('Module:Arguments').getArgslocal str = require('Module:String')local yesno = require('Module:Yesno')local defaultSep = ','local iMaxWords = 16local warningIMaxWordsReached = nillocal xpLitWordCount = 0local report -- to be initinated when explain needed
-- Initialise the /report subpage.-- only invoked when 'explain' askedlocal function initReport report = require('Module:Str find word/report')end
-- Turn "A" into "A" etc. asap-- and reduce multi-spaces (including nbsp etc.) into single spacelocal function decodeUnicode(str) return mw.ustring.gsub(mw.text.decode(str), '%s+', ' ')end
-- %-Escape any word (character string) before feeding it into a string pattern function-- all punctuation (%p) will be %-escapedlocal function escape_word(word) return str._escapePattern(word)end
-- 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-- step 1: when case-insensitive, turn string into lowercase-- step 2: read & remove Literals ("..")-- step 3: read comma-separated words-- step 4: when booleans=T, change boolean words into true/false (module:yesno rules)-- all words returned are trimmed, TODO and all ws into single-plainspace?-- only T/F words are edited, other words remain, untouched-- return the table (a straight array)local function buildWordTable(tArgs, sWordlist)local wordTable = local hitWord = local hitCount = 0 if sWordlist
-- Step 1: case-sensitive if yesno(tArgs.case, true)
-- Step 2: read "literals", -- then remove them from the string: -- replaced by single comma; idle & keeps word separation --- 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
while sCount > 1 do -- could do here: only when even? i = string.find(sWordlist, '%"', 1, false) j = string.find(sWordlist, '%"', i+1, false) litWord = mw.text.trim(string.sub(sWordlist, i+1, j-1)) if #litWord > 0 then -- not an empty string or spaces only xpLitWordCount = xpLitWordCount + 1 table.insert(wordTable, litWord) end -- remove from source, and do next gsub search: sWordlist = string.gsub(sWordlist, '%"%s*' .. escape_word(litWord) .. '%s*%"', ',') _, sCount = mw.ustring.gsub(sWordlist, '"', ) end end end -- Step 3: parse comma-delimited words hitCount = 0 sWordlist = tArgs.sep .. sWordlist .. tArgs.sep local eSep eSep = escape_word(tArgs.sep) local patstring = '%f[^' .. eSep .. '][^' .. eSep .. ']+%f[' .. eSep .. ']' if yesno(tArgs.explain, false) then report.xpMessage('1.eSep: ' .. eSep) -- dev report.xpMessage('2.pattern: ' .. patstring) -- dev end while hitCount <= iMaxWords do hitCount = hitCount + 1 hitWord = str._match(sWordlist, patstring, 1, hitCount, false, tArgs.sep) hitWord = mw.text.trim(hitWord) if hitWord
-- Step 4: when read booleans, converse words to true/false -- todo: check parameter here not elsewhere if tArgs.booleans then -- TODO if Yesno(tArgs.booleans) ... local sBool for i, v in ipairs(wordTable) do sBool = yesno(v) if sBool ~= nil then wordTable[i] = tostring(sBool) end end end
return wordTableend
-- Check whether a single word is in a table (a simple array of words)-- returns hitword or nillocal function findWordInTable(sourceWordTable, word) local bHit = false for i, v in ipairs(sourceWordTable) do if v
-- AND-logic with andWordTable words: ALL words must be found-- returns -- T when *all* AND words are found-- hittable with all hit words-- note 1: when F, the hittable still contains the words that were found-- note 2: empty AND-wordlist => True by logic (because: not falsified)local function checkANDwords(sourceWordTable, andWordTable)local result1local bANDlocal tHits
bAND = true tHits = result1 = nil if #andWordTable > 0 then for i, word in ipairs(andWordTable) do result1 = findWordInTable(sourceWordTable, word) or nil if result1
return bAND, tHitsend
-- OR-logic with orWordTable words: at least one word must be found-- returns -- True when at least one OR word is found-- hittable has all hit words-- 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 foundlocal function checkORwords(sourceWordTable, orWordTable)local result1local bORlocal tHits
bOR = false tHits = result1 = nil if #orWordTable > 0 then for i, word in ipairs(orWordTable) do result1 = findWordInTable(sourceWordTable, word) or nil if result1
return bOR, tHitsend
-- Determine the requested return value (string).-- sYeslist is the _main return value (logically defined value)-- this function applies tArgs.yes / tArgs.no return value-- note: yes= implies: blank return value-- note: no parameter yes= (that is, yes=nil) implies: by default, return the sYeslistlocal function yesnoReturnstring(tArgs, sYeslist) if sYeslist
nil then return sYeslist else -- some |yes= value is entered, could be return tArgs.yes end endend
local function isPreviewlocal ifPreview = require('Module:If preview') return not (ifPreview._warning
-- Explain options (=report info), interprets parameter explain=-- returns true/false/'testcases'-- explain=true => show report in Preview-- explain=testcases => WHEN in ns: template: or user: AND subpage = '/testcases' THEN show permanentlylocal function checkExplain(tArgs) return false -- never. 22Mar2023 checkExplain(newArgs)end
--
sourceWordTable = buildWordTable(tArgs, tArgs.source) andWordTable = buildWordTable(tArgs, tArgs.andString) orWordTable = buildWordTable(tArgs, tArgs.orString)
if (#sourceWordTable
0) then -- No words to check resultALL = false if yesno(tArgs.explain, false) then report.xpNoWords(tArgs, sourceWordTable, andWordTable, orWordTable) end else bANDresult, tANDhits = checkANDwords(sourceWordTable, andWordTable) bORresult, tORhits = checkORwords(sourceWordTable, orWordTable) resultALL = (bANDresult) and (bORresult) end
sYeslist = if resultALL then -- concat the sYeslist (= all hit words; from 2 tables) 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 if yesno(tArgs.explain, false) then if tArgs.yes ~= nil then if (tArgs.yes
) 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
-- set wordt separator local function setSep(sSep) if sSep
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
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
local function parseArgs(origArgs)local newArgs = newArgs['sep'] = setSep(origArgs['sep']) -- do first, needed below newArgs['source'] = decodeUnicode(origArgs['s'] or origArgs['source'] or ) newArgs['andString'] = decodeUnicode(concatAndLists(origArgs['w'] or origArgs['word'] or nil, origArgs['andw'] or origArgs['andwords'] or nil, newArgs.sSep) ) newArgs['orString'] = decodeUnicode(origArgs['orw'] or origArgs['orwords'] or ) -- boolean options: catch both parameters, also handle nil & nonsense input values: newArgs['case'] = yesno(origArgs['case'] or origArgs['casesensitive'] or true, true) -- defaults to True newArgs['booleans'] = yesno(origArgs['bool'] or origArgs['booleans'] or false, false) -- defaults to False newArgs['literals'] = yesno(origArgs['literals'] or origArgs['lit'] or true, true) -- defaults to True 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)
newArgs.explain = false -- never. 22Mar2023 checkExplain(newArgs) return newArgsend
function p.main(frame)local origArgs = getArgs(frame)local sReturn = local tArgs =
tArgs = parseArgs(origArgs) if yesno(tArgs.explain, false) then initReport report.xpListArguments(origArgs) end
sReturn = p._main(tArgs) if warningIMaxWordsReached ~=nil then local preview = require('Module:If preview') sReturn = sReturn .. preview._warning end
if yesno(tArgs.explain, false) then return sReturn .. report.xpPresent(tArgs.explain) else return sReturn endend
return p