This module is rated as pre-alpha. It is unfinished, and may or may not be in active development. It should not be used from article namespace pages. Modules remain pre-alpha until the original editor (or someone who takes one over if it is abandoned for some time) is satisfied with the basic structure.
This module depends on the following other modules:
This module looks for a full word 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.
The search can be extended to check for multiple words being present.
For example, in the string 'alpha, foobar' the word 'alpha' appears, but the word 'foo' does not: there only is the complete word 'foobar'. The module is aimed at template code usage. (The editor does not have to apply Lua patterns like [%a%d]*).
--- STABLE: 16-11-2021 20:30-- todo: sep = len 1 max-- not %escape then?--- test {{!}} require('Module:No globals')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 /report subpage.-- only invoked when needed when explain askedlocalfunctioninitReport()report=require('Module:Str find word/report')end-- Turn "#x0041;" 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: reads comma-separated words-- step 4: when booleans=T, change boolean words into true/false (module:yesno rules)-- all words returned are trimmed-- only T/F words are edited, other words rtemain, untouched-- return the table (a straight array)localfunctionbuildWordTable(tArgs,sWordlist)localwordTable={}localhitWord=''localhitCount=0ifsWordlist==''thenreturnwordTableend-- Step 1: case-sensitiveifyesno(tArgs.case,false)==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,true)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 to words true/falseiftArgs.booleansthenlocalsBoolfori,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)ifyesno(tArgs.explain,true)thenifyesno(tArgs.explain,false)==truethen-- explicit True so preview showifisPreview()==truethenreturntrueendelseiftArgs.explain=='testcases'thenlocaltitleObj=mw.title.getCurrentTitle()iftitleObj:inNamespaces('template','user')andtitleObj.subpageText=='testcases'andtitleObj.isSubpagethenreturn'testcases'endendendreturnfalseend-- ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====-- _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,true)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,true)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']orfalse,false)-- defaults to FalsenewArgs['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']=origArgs['explain']orfalsenewArgs.explain=checkExplain(newArgs)returnnewArgsendfunctionp.main(frame)localorigArgs=getArgs(frame)localsReturn=''localtArgs={}tArgs=parseArgs(origArgs)ifyesno(tArgs.explain,true)theninitReport()report.xpListArguments(origArgs)endsReturn=p._main(tArgs)ifwarningIMaxWordsReached~=nilthenlocalpreview=require('Module:If preview')sReturn=sReturn..preview._warning({warningIMaxWordsReached})endifyesno(tArgs.explain,true)thenreturnsReturn..report.xpPresent(tArgs.explain)elsereturnsReturnendendreturnp