This module looks for a word being present in a comma-separated list of words. It then returns a True or False value. By default, the True-value returned is the found word itself; the False-value is a blank string.
For example, in the source string 'foo, bar' the word 'bar' appears, but the word 'november' does not.
{{#invoke:Str find word |main |source=foo, bar |word=bar}} (True) → bar
{{#invoke:Str find word |main |source=alpha, beta, gamma |word=november}} (False) →
Multiple word check: The search can be extended to check for multiple words being present.
require('strict')localp={}localgetArgs=require('Module:Arguments').getArgslocalstr=require('Module:String')localyesno=require('Module:Yesno')localdefaultSep=','localiMaxWords=16localwarningIMaxWordsReached=nillocalxpLitWordCount=0localreport-- to be initinated when explain needed-- Initialise the /report subpage.-- only invoked when 'explain' askedlocalfunctioninitReport()report=require('Module:Str find word/report')end-- Turn "A" into "A" etc. asap-- and reduce multi-spaces (including nbsp etc.) into single spacelocalfunctiondecodeUnicode(str)returnmw.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 %-escapedlocalfunctionescape_word(word)returnstr._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)localfunctionbuildWordTable(tArgs,sWordlist)localwordTable={}localhitWord=''localhitCount=0ifsWordlist==''thenreturnwordTableend-- Step 1: case-sensitiveifyesno(tArgs.case,true)==falsethensWordlist=string.lower(sWordlist)end-- Step 2: read "literals", -- then remove them from the string:-- replaced by single comma; idle & keeps word separation--- if yesno(tArgs.literals, false) theniffalsethenlocal_,sCount_,sCount=mw.ustring.gsub(sWordlist,'"','')ifsCount>1thenlocallitWord=''locali,jwhilesCount>1do-- 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>0then-- not an empty string or spaces onlyxpLitWordCount=xpLitWordCount+1table.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,'"','')endendend-- Step 3: parse comma-delimited wordshitCount=0sWordlist=tArgs.sep..sWordlist..tArgs.seplocaleSepeSep=escape_word(tArgs.sep)localpatstring='%f[^'..eSep..'][^'..eSep..']+%f['..eSep..']'ifyesno(tArgs.explain,false)thenreport.xpMessage('1.eSep: '..eSep)-- devreport.xpMessage('2.pattern: '..patstring)-- devendwhilehitCount<=iMaxWordsdohitCount=hitCount+1hitWord=str._match(sWordlist,patstring,1,hitCount,false,tArgs.sep)hitWord=mw.text.trim(hitWord)ifhitWord==tArgs.septhen-- no more words found in the stringbreakelseifhitWord~=''thentable.insert(wordTable,hitWord)endendifhitCount>iMaxWordsthenwarningIMaxWordsReached='Max number of words ('..tostring(iMaxWords)..') reached. Extra words are ignored.'..' ('..mw.ustring.sub(mw.text.trim(sWordlist),1,90)..' ...). 'end-- Step 4: when read booleans, converse words to true/false-- todo: check parameter here not elsewhereiftArgs.booleansthen-- TODO if Yesno(tArgs.booleans) ... localsBoolfori,vinipairs(wordTable)dosBool=yesno(v)ifsBool~=nilthenwordTable[i]=tostring(sBool)endendendreturnwordTableend-- Check whether a single word is in a table (a simple array of words)-- returns hitword or nillocalfunctionfindWordInTable(sourceWordTable,word)localbHit=falsefori,vinipairs(sourceWordTable)doifv==wordthenbHit=truebreakendendifbHitthenreturnwordelsereturnnilendend-- AND-logic with andWordTable words: ALL words must be found-- returns {T/F, hittable}-- 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)localfunctioncheckANDwords(sourceWordTable,andWordTable)localresult1localbANDlocaltHitsbAND=truetHits={}result1=nilif#andWordTable>0thenfori,wordinipairs(andWordTable)doresult1=findWordInTable(sourceWordTable,word)ornilifresult1==nilthenbAND=false-- Falsified!-- could break after this logically but -- continue to complete the table (bAND remains false)elsetable.insert(tHits,result1)endendelsebAND=trueendreturnbAND,tHitsend-- OR-logic with orWordTable words: at least one word must be found-- returns {T/F, hittable}-- 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 foundlocalfunctioncheckORwords(sourceWordTable,orWordTable)localresult1localbORlocaltHitsbOR=falsetHits={}result1=nilif#orWordTable>0thenfori,wordinipairs(orWordTable)doresult1=findWordInTable(sourceWordTable,word)ornilifresult1==nilthen-- this one is false; bOR unchanged; do nextelsebOR=true-- Confirmed!table.insert(tHits,result1)-- could break here logically, but complete the checkendendelsebOR=trueendreturnbOR,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 sYeslistlocalfunctionyesnoReturnstring(tArgs,sYeslist)ifsYeslist==''then-- False returntArgs.noor''else-- TrueiftArgs.yes==nilthenreturnsYeslistelse-- some |yes= value is entered, could be ''returntArgs.yesendendendlocalfunctionisPreview()localifPreview=require('Module:If preview')returnnot(ifPreview._warning({'is_preview'})=='')end-- 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 permanentlylocalfunctioncheckExplain(tArgs)returnfalse-- never. 22Mar2023 checkExplain(newArgs)end-- ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====-- _main function: check for presence of words in source string-- Checks and returns:-- when T: the string of all hitwords (default), or the |yes=... input-- when F: empty string '' (default), or the |no=... input-- steps:-- 1. input word strings are prepared (parsed into an array of words)-- 2. words checks are made (applying AND-logic, OR-logic)-- 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)functionp._main(tArgs)localsourceWordTable={}localandWordTable={}localorWordTable={}localtANDhitslocaltORhits-- logical finding:localbANDresult=falselocalbORresult=falselocalresultALL=falselocalsYeslist=''sourceWordTable=buildWordTable(tArgs,tArgs.source)andWordTable=buildWordTable(tArgs,tArgs.andString)orWordTable=buildWordTable(tArgs,tArgs.orString)if(#sourceWordTable==0)or(#andWordTable+#orWordTable==0)then-- No words to checkresultALL=falseifyesno(tArgs.explain,false)thenreport.xpNoWords(tArgs,sourceWordTable,andWordTable,orWordTable)endelsebANDresult,tANDhits=checkANDwords(sourceWordTable,andWordTable)bORresult,tORhits=checkORwords(sourceWordTable,orWordTable)resultALL=(bANDresult)and(bORresult)endsYeslist=''ifresultALLthen-- concat the sYeslist (= all hit words; from 2 tables)ifbANDresultthensYeslist=sYeslist..table.concat(tANDhits,tArgs.sep)endif#tORhits>0thenif#tANDhits>0thensYeslist=sYeslist..tArgs.sependsYeslist=sYeslist..table.concat(tORhits,tArgs.sep)endendifyesno(tArgs.explain,false)theniftArgs.yes~=nilthenif(tArgs.yes=='')and(tArgs.no=='')thenreport.xpYesNoBothBlank()endendifwarningIMaxWordsReached~=nilthenreport.xpMessage(warningIMaxWordsReached)endreport.xpBuildReport(tArgs,sourceWordTable,bANDresult,andWordTable,tANDhits,bORresult,orWordTable,tORhits,sYeslist,xpLitWordCount)endreturnyesnoReturnstring(tArgs,sYeslist)end-- set wordt separator localfunctionsetSep(sSep)ifsSep==nilthenreturndefaultSependlocalmsg=''-- todo what with {{!}}localnewSep=defaultSepnewSep=sSepsSep=decodeUnicode(sSep)ifstring.match(sSep,'[%s%w%d]')~=nilthen-- not okmsg='Irregular characters in sep: '..sSepnewSep=defaultSependnewSep=string.sub(sSep,1,1)ifnewSep==''then--- ???newSep=defaultSependreturnnewSependlocalfunctionconcatAndLists(s1,s2,newSep)localtLists={}-- working table: both s1 and s2 to concattable.insert(tLists,s1)table.insert(tLists,s2)returntable.concat(tLists,newSep)endlocalfunctionparseArgs(origArgs)localnewArgs={}newArgs['sep']=setSep(origArgs['sep'])-- do first, needed belownewArgs['source']=decodeUnicode(origArgs['s']ororigArgs['source']or'')newArgs['andString']=decodeUnicode(concatAndLists(origArgs['w']ororigArgs['word']ornil,origArgs['andw']ororigArgs['andwords']ornil,newArgs.sSep))newArgs['orString']=decodeUnicode(origArgs['orw']ororigArgs['orwords']or'')-- boolean options: catch both parameters, also handle nil & nonsense input values:newArgs['case']=yesno(origArgs['case']ororigArgs['casesensitive']ortrue,true)-- defaults to TruenewArgs['booleans']=yesno(origArgs['bool']ororigArgs['booleans']orfalse,false)-- defaults to FalsenewArgs['literals']=yesno(origArgs['literals']ororigArgs['lit']ortrue,true)-- defaults to TruenewArgs['yes']=origArgs['yes']ornil-- nil; default so return sYeslist; keep '' as legal input & return valuenewArgs['no']=origArgs['no']or''newArgs['explain']=false-- never. 22Mar2023 checkExplain(newArgs)newArgs.explain=false-- never. 22Mar2023 checkExplain(newArgs)returnnewArgsendfunctionp.main(frame)localorigArgs=getArgs(frame)localsReturn=''localtArgs={}tArgs=parseArgs(origArgs)ifyesno(tArgs.explain,false)theninitReport()report.xpListArguments(origArgs)endsReturn=p._main(tArgs)ifwarningIMaxWordsReached~=nilthenlocalpreview=require('Module:If preview')sReturn=sReturn..preview._warning({warningIMaxWordsReached})endifyesno(tArgs.explain,false)thenreturnsReturn..report.xpPresent(tArgs.explain)elsereturnsReturnendendreturnp