Module:Transcluder
- Afrikaans
- العربية
- অসমীয়া
- Avañe'ẽ
- Авар
- تۆرکجه
- Basa Bali
- বাংলা
- Basa Banyumasan
- Беларуская
- Беларуская (тарашкевіца)
- Bosanski
- Català
- Chavacano de Zamboanga
- Dansk
- الدارجة
- Deutsch
- Eesti
- Ελληνικά
- Español
- Esperanto
- Euskara
- فارسی
- Français
- Galego
- ગુજરાતી
- गोंयची कोंकणी / Gõychi Konknni
- 한국어
- Hausa
- Hrvatski
- Ilokano
- Bahasa Indonesia
- Íslenska
- Italiano
- Kurdî
- Ladin
- Lietuvių
- Magyar
- Македонски
- മലയാളം
- ဘာသာမန်
- Bahasa Melayu
- မြန်မာဘာသာ
- Nederlands
- नेपाली
- 日本語
- Нохчийн
- Oʻzbekcha / ўзбекча
- ਪੰਜਾਬੀ
- ភាសាខ្មែរ
- Português
- Română
- Русский
- Scots
- Shqip
- සිංහල
- Simple English
- سنڌي
- Slovenčina
- Slovenščina
- کوردی
- Српски / srpski
- தமிழ்
- တႆး
- ไทย
- Türkçe
- Українська
- اردو
- Tiếng Việt
- 吴语
- Yorùbá
- 粵語
- 中文
- Betawi
- Jaku Iban
See the protection policy and protection log for more details. Please discuss any changes on the talk page; you may submit an edit request to ask an administrator to make an edit if it is uncontroversial or supported by consensus. You may also request that this page be unprotected.
This module is a general-purpose transclusion engine, able to transclude any part of any page and with many options that normal transclusion doesn't provide.
Usage
Modules
The main entry point for modules is the get
method.
get( 'Title' )
— Get the requested page (exact same result as normal transclusion)get( 'Title#' )
— Get the lead section of the requested pageget( 'Title#Section' )
— Get the requested section or <section> tag (includes any subsections)
<noinclude> and <onlyinclude> tags are handled the usual way and there's also an optional second parameter to exclude various elements from the result:
get( 'Title#Section', { files = 0 } )
— Exclude all filesget( 'Title#Section', { files = 1 } )
— Exclude all files except the firstget( 'Title#Section', { files = 2 } )
— Exclude all files except the secondget( 'Title#Section', { files = '1,2' } )
— Exclude all files except the first and secondget( 'Title#Section', { files = '1-3' } )
— Exclude all files except the first, second and thirdget( 'Title#Section', { files = '1,3-5' } )
— Exclude all files except the first, third, fourth and fifthget( 'Title#Section', { files = -2 } )
— Exclude the second fileget( 'Title#Section', { files = '-2,3' } )
— Exclude the second and third filesget( 'Title#Section', { files = '-1,3-5' } )
— Exclude the first, third, fourth and fifth filesget( 'Title#Section', { files = 'A.png' } )
— Exclude all files except A.pngget( 'Title#Section', { files = '-A.png' } )
— Exclude A.pngget( 'Title#Section', { files = 'A.png, B.jpg, C.gif' } )
— Exclude all files except A.png, B.jpg and C.gifget( 'Title#Section', { files = '-A.png, B.jpg, C.gif' } )
— Exclude A.png, B.jpg and C.gifget( 'Title#Section', { files = { [1] = true, [3] = true } } )
— Exclude all files except the first and thirdget( 'Title#Section', { files = { [1] = false, [3] = false } } )
— Exclude the first and third filesget( 'Title#Section', { files = { ['A.png'] = false, ['B.jpg'] = false } } )
— Exclude A.png and B.jpgget( 'Title#Section', { files = '.+%.png' } )
— Exclude all files except PNG files (see Lua patterns)get( 'Title#Section', { files = '-.+%.png' } )
— Exclude all PNG files
The very same syntax can be used to exclude many other elements:
get( 'Title#Section', { sections = 0 } )
— Exclude all subsectionsget( 'Title#Section', { sections = 'History, Causes' } )
— Exclude all subsections except 'History' and 'Causes'get( 'Title#Section', { lists = 1 } )
— Exclude all lists except the firstget( 'Title#Section', { tables = 'stats' } )
— Exclude all tables except the one with id 'stats'get( 'Title#Section', { paragraphs = '1-3' } )
— Exclude all paragraphs except the first, second and thirdget( 'Title#Section', { references = 0 } )
— Exclude all referencesget( 'Title#Section', { categories = '0' } )
— Exclude all categoriesget( 'Title#Section', { templates = '-.+infobox' } )
— Exclude infobox templatesget( 'Title#Section', { parameters = 'image' } )
— Exclude all parameters from all templates except the one named 'image'
Options can be combined at will. For example:
get( 'Title#Section', { sections = 0, files = 1, paragraphs = '1-3' } )
— Exclude all subsections, all files except the first, and all paragraphs except the first three
You can also get only some elements like so:
get( 'Title#Section', { only = 'files' } )
— Get only the filesget( 'Title#Section', { only = 'lists', lists = 1 } )
— Get only the first listget( 'Title#Section', { only = 'tables', tables = 'stats' } )
— Get only the table with id 'stats'get( 'Title#Section', { only = 'paragraphs', paragraphs = '1,3-5' } )
— Get only the first, third, fourth and fifth paragraphget( 'Title#Section', { only = 'templates', templates = 'Infobox' } )
— Get only the infoboxget( 'Title#Section', { only = 'parameters', parameters = 'abstract', references = 0 } )
— Get only the parameter called 'abstract' and remove all references from it
The output can be further modified with a few special options:
get( 'Title#Section', { noFollow = true } )
— Don't follow redirectsget( 'Title#Section', { linkBold = true } )
— Link the bold title or synonym near the start of the textget( 'Title#Section', { noBold = true } )
— Remove bold textget( 'Title#Section', { noComments = true } )
— Remove all HTML commentsget( 'Title#Section', { noLinks = true } )
— Remove all linksget( 'Title#Section', { noSelfLinks = true } )
— Remove self linksget( 'Title#Section', { noBehaviorSwitches = true } )
— Remove behavior switches such as__NOTOC__
get( 'Title#Section', { noNonFreeFiles = true } )
— Remove non-free files (identified by having the words "non-free" in their local description or in Commons)get( 'Title#Section', { fixReferences = true } )
— Prefix reference names with 'Title ' to avoid name conflicts when transcluding and rescue references defined outside the requested section to avoid undefined reference errors
Besides the get
method, the module exposes several other methods to get specific parts of the wikitext. This allows other modules to combine elements in more advanced ways.
Templates
The main entry point for templates is the main
method. It's essentially a wrapper of the get
method to make it usable for templates. See the documentation of the get
method for more details and options.
{{#invoke:Transcluder|main|Title}}
— Transclude the requested page{{#invoke:Transcluder|main|Title#}}
— Transclude the lead section of the requested page{{#invoke:Transcluder|main|Title#Section}}
— Get the requested section or <section> tag (includes any subsections){{#invoke:Transcluder|main|Title#Section|sections=0}}
— Transclude the requested section, excluding subsections{{#invoke:Transcluder|main|Title|only=files|files=1}}
— Transclude only the first file of the page{{#invoke:Transcluder|main|Title#Section|only=tables|tables=2}}
— Transclude only the second table of the requested section{{#invoke:Transcluder|main|Title#|only=paragraphs|linkBold=yes}}
— Transclude only the paragraphs of the lead section and link the bold text
See also
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Subpages of this module.
-- Module:Transcluder is a general-purpose transclusion engine -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Transcluder -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-3.0 localp={} -- Helper function to test for truthy and falsy values -- @todo Somehow internationalize it localfunctiontruthy(value) ifnotvalueorvalue==''orvalue==0orvalue=='0'orvalue=='false'orvalue=='no'orvalue=='non'then returnfalse end returntrue end -- Helper function to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... localfunctionmatchAny(text,pre,list,post,init) localmatch={} fori=1,#listdo match={mw.ustring.match(text,pre..list[i]..post,init)} ifmatch[1]thenreturnunpack(match)end end returnnil end -- Like matchAny but for Category/File links with less overhead localfunctionmatchAnyLink(text,list) localmatch for_,vinipairs(list)do match=string.match(text,'%[%[%s*'..v..'%s*:.*%]%]') ifmatchthenbreakend end returnmatch end -- Helper function to escape a string for use in regexes localfunctionescapeString(str) returnstring.gsub(str,'[%^%$%(%)%.%[%]%*%+%-%?%%]','%%%0') end -- Helper function to remove a string from a text localfunctionremoveString(text,str) localpattern=escapeString(str) if#pattern>9999then-- strings longer than 10000 bytes can't be put into regexes pattern=escapeString(mw.ustring.sub(str,1,999))..'.-'..escapeString(mw.ustring.sub(str,-999)) end returnstring.gsub(text,pattern,'') end -- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param flags Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the flags should be treated as a blacklist or not localfunctionparseFlags(value) localflags={} localblacklist=false ifnotvaluethenreturnnil,falseend iftype(value)=='number'then ifvalue<0then value=-value blacklist=true end flags={[value]=true} elseiftype(value)=='string'then ifstring.sub(value,1,1)=='-'then blacklist=true value=string.sub(value,2) end localranges=mw.text.split(value,',')-- split ranges: '1,3-5' to {'1','3-5'} for_,rangeinpairs(ranges)do range=mw.text.trim(range) localmin,max=mw.ustring.match(range,'^(%d+)%s*[-–—]%s*(%d+)$')-- '3-5' to min=3 max=5 ifnotmaxthenmin,max=string.match(range,'^((%d+))$')end-- '1' to min=1 max=1 ifmaxthen fori=min,maxdoflags[i]=trueend else flags[range]=true-- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseiftype(value)=='table'then fori,vinpairs(value)do ifv==falsethenblacklist=trueend flags[i]=true end end returnflags,blacklist end -- Helper function to see if a value matches any of the given flags localfunctionmatchFlag(value,flags) ifnotvaluethenreturnfalseend value=tostring(value) locallang=mw.language.getContentLanguage() locallcvalue=lang:lcfirst(value) localucvalue=lang:ucfirst(value) forflaginpairs(flags)do ifvalue==tostring(flag) orlcvalue==flag orucvalue==flag or(nottonumber(flag)andmw.ustring.match(value,flag))then returntrue end end end -- Helper function to convert template arguments into an array of options fit for get() localfunctionparseArgs(frame) localargs={} forkey,valueinpairs(frame:getParent().args)doargs[key]=valueend forkey,valueinpairs(frame.args)doargs[key]=valueend-- args from Lua calls have priority over parent args from template returnargs end -- Error handling function -- Throws a Lua error or returns an empty string if error reporting is disabled localfunctionthrowError(key,value) localTNT=require('Module:TNT') localok,message=pcall(TNT.format,'I18n/Module:Transcluder.tab','error-'..key,value) ifnotokthenmessage=keyend error(message,2) end -- Error handling function -- Returns a wiki friendly error or an empty string if error reporting is disabled localfunctiongetError(key,value) localTNT=require('Module:TNT') localok,message=pcall(TNT.format,'I18n/Module:Transcluder.tab','error-'..key,value) ifnotokthenmessage=keyend message=mw.html.create('div'):addClass('error'):wikitext(message) returnmessage end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} localfunctiongetNamespaces(name) localnamespaces=mw.clone(mw.site.namespaces[name].aliases)-- Clone because https://en.wikipedia.org/w/index.php?diff=1056921358 table.insert(namespaces,mw.site.namespaces[name].name) table.insert(namespaces,mw.site.namespaces[name].canonicalName) returnnamespaces end -- Get the page wikitext, following redirects -- Also returns the page name, or the target page name if a redirect was followed, or false if no page was found -- For file pages, returns the content of the file description page localfunctiongetText(page,noFollow) page=mw.text.decode(page) localtitle=mw.title.new(page) ifnottitlethenreturnfalse,falseend localtarget=title.redirectTarget iftargetandnotnoFollowthentitle=targetend localtext=title:getContent() ifnottextthenreturnfalse,title.prefixedTextend -- Remove <noinclude> tags text=string.gsub(text,'<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>','')-- remove noinclude bits -- Keep <onlyinclude> tags ifstring.find(text,'onlyinclude')then-- avoid expensive search if possible text=text :gsub('</onlyinclude>.-<onlyinclude>','')-- remove text between onlyinclude sections :gsub('^.-<onlyinclude>','')-- remove text before first onlyinclude section :gsub('</onlyinclude>.*','')-- remove text after last onlyinclude section end returntext,title.prefixedText end -- Get the requested files from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of files to return, for example 2 or '1,3-5'. Omit to return all files. -- @return Sequence of strings containing the wikitext of the requested files. -- @return Original wikitext minus requested files. localfunctiongetFiles(text,flags) localfiles={} localflags,blacklist=parseFlags(flags) localfileNamespaces=getNamespaces('File') localname localcount=0 forfileinstring.gmatch(text,'%b[]')do ifmatchAnyLink(file,fileNamespaces)then name=string.match(file,'%[%[[^:]-:([^]|]+)') count=count+1 ifnotblacklistand(notflagsorflags[count]ormatchFlag(name,flags)) orblacklistandflagsandnotflags[count]andnotmatchFlag(name,flags)then table.insert(files,file) else text=removeString(text,file) end end end returnfiles,text end -- Get the requested tables from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of tables to return, for example 2 or '1,3-5'. Omit to return all tables. -- @return Sequence of strings containing the wikitext of the requested tables. -- @return Original wikitext minus requested tables. localfunctiongetTables(text,flags) localtables={} localflags,blacklist=parseFlags(flags) localid localcount=0 fortinstring.gmatch('\n'..text,'\n%b{}')do ifstring.sub(t,1,3)=='\n{|'then id=string.match(t,'\n{|[^\n]-id%s*=%s*["\']?([^"\'\n]+)["\']?[^\n]*\n') count=count+1 ifnotblacklistand(notflagsorflags[count]orflags[id]) orblacklistandflagsandnotflags[count]andnotflags[id]then table.insert(tables,t) else text=removeString(text,t) end end end returntables,text end -- Get the requested templates from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of templates to return, for example 2 or '1,3-5'. Omit to return all templates. -- @return Sequence of strings containing the wikitext of the requested templates. -- @return Original wikitext minus requested templates. localfunctiongetTemplates(text,flags) localtemplates={} localflags,blacklist=parseFlags(flags) localname localcount=0 fortemplateinstring.gmatch(text,'{%b{}}')do ifstring.sub(template,1,3)~='{{#'then-- skip parser functions like #if name=mw.text.trim(string.match(template,'{{([^}|\n]+)')or"")-- get the template name ifname~=""then count=count+1 ifnotblacklistand(notflagsorflags[count]ormatchFlag(name,flags)) orblacklistandflagsandnotflags[count]andnotmatchFlag(name,flags)then table.insert(templates,template) else text=removeString(text,template) end end end end returntemplates,text end -- Get the requested template parameters from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of parameters to return, for example 2 or '1,3-5'. Omit to return all parameters. -- @return Map from parameter name to value, NOT IN THE ORIGINAL ORDER -- @return Original wikitext minus requested parameters. -- @return Order in which the parameters were parsed. localfunctiongetParameters(text,flags) localparameters,parameterOrder={},{} localflags,blacklist=parseFlags(flags) localparams,count,parts,key,value fortemplateinstring.gmatch(text,'{%b{}}')do params=string.match(template,'{{[^|}]-|(.*)}}') ifparamsthen count=0 -- Temporarily replace pipes in subtemplates and links to avoid chaos forsubtemplateinstring.gmatch(params,'{%b{}}')do params=string.gsub(params,escapeString(subtemplate),string.gsub(subtemplate,".",{["%"]="%%",["|"]="@@:@@",["="]="@@_@@"})) end forlinkinstring.gmatch(params,'%b[]')do params=string.gsub(params,escapeString(link),string.gsub(link,".",{["%"]="%%",["|"]="@@:@@",["="]="@@_@@"})) end forparameterinmw.text.gsplit(params,'|')do parts=mw.text.split(parameter,'=') key=mw.text.trim(parts[1]) if#parts==1then value=key count=count+1 key=count else value=mw.text.trim(table.concat(parts,'=',2)) end value=string.gsub(string.gsub(value,'@@:@@','|'),'@@_@@','=') ifnotblacklistand(notflagsormatchFlag(key,flags)) orblacklistandflagsandnotmatchFlag(key,flags)then table.insert(parameterOrder,key) parameters[key]=value else text=removeString(text,parameter) end end end end returnparameters,text,parameterOrder end -- Get the requested lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of lists to return, for example 2 or '1,3-5'. Omit to return all lists. -- @return Sequence of strings containing the wikitext of the requested lists. -- @return Original wikitext minus requested lists. localfunctiongetLists(text,flags) locallists={} localflags,blacklist=parseFlags(flags) localcount=0 forlistinstring.gmatch('\n'..text..'\n\n','\n([*#].-)\n[^*#]')do count=count+1 ifnotblacklistand(notflagsorflags[count]) orblacklistandflagsandnotflags[count]then table.insert(lists,list) else text=removeString(text,list) end end returnlists,text end -- Get the requested paragraphs from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of paragraphs to return, for example 2 or '1,3-5'. Omit to return all paragraphs. -- @return Sequence of strings containing the wikitext of the requested paragraphs. -- @return Original wikitext minus requested paragraphs. localfunctiongetParagraphs(text,flags) localparagraphs={} localflags,blacklist=parseFlags(flags) -- Remove non-paragraphs localelements localtemp='\n'..text..'\n' elements,temp=getLists(temp,0)-- remove lists elements,temp=getFiles(temp,0)-- remove files temp=mw.text.trim((temp :gsub('\n%b{} *\n','\n%0\n')-- add spacing between tables and block templates :gsub('\n%b{} *\n','\n')-- remove tables and block templates :gsub('\n==+[^=]+==+ *\n','\n')-- remove section titles )) -- Assume that anything remaining is a paragraph localcount=0 forparagraphinmw.text.gsplit(temp,'\n\n+')do ifmw.text.trim(paragraph)~=''then count=count+1 ifnotblacklistand(notflagsorflags[count]) orblacklistandflagsandnotflags[count]then table.insert(paragraphs,paragraph) else text=removeString(text,paragraph) end end end returnparagraphs,text end -- Get the requested categories from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of categories to return, for example 2 or '1,3-5'. Omit to return all categories. -- @return Sequence of strings containing the wikitext of the requested categories. -- @return Original wikitext minus requested categories. localfunctiongetCategories(text,flags) localcategories={} localflags,blacklist=parseFlags(flags) localcategoryNamespaces=getNamespaces('Category') localname localcount=0 forcategoryinstring.gmatch(text,'%b[]')do ifmatchAnyLink(category,categoryNamespaces)then name=string.match(category,'%[%[[^:]-:([^]|]+)') count=count+1 ifnotblacklistand(notflagsorflags[count]ormatchFlag(name,flags)) orblacklistandflagsandnotflags[count]andnotmatchFlag(name,flags)then table.insert(categories,category) else text=removeString(text,category) end end end returncategories,text end -- Get the requested references from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of references to return, for example 2 or '1,3-5'. Omit to return all references. -- @return Sequence of strings containing the wikitext of the requested references. -- @return Original wikitext minus requested references. localfunctiongetReferences(text,flags) localreferences={} -- Remove all references, including citations, when 0 references are requested -- This is kind of hacky but currently necessary because the rest of the code -- doesn't remove citations like <ref name="Foo" /> if Foo is defined elsewhere ifflagsandnottruthy(flags)then text=string.gsub(text,'<%s*[Rr][Ee][Ff][^>/]*>.-<%s*/%s*[Rr][Ee][Ff]%s*>','') text=string.gsub(text,'<%s*[Rr][Ee][Ff][^>/]*/%s*>','') returnreferences,text end localflags,blacklist=parseFlags(flags) localname localcount=0 forreferenceinstring.gmatch(text,'<%s*[Rr][Ee][Ff][^>/]*>.-<%s*/%s*[Rr][Ee][Ff]%s*>')do name=string.match(reference,'<%s*[Rr][Ee][Ff][^>]*name%s*=%s*["\']?([^"\'>/]+)["\']?[^>]*%s*>') count=count+1 ifnotblacklistand(notflagsorflags[count]ormatchFlag(name,flags)) orblacklistandflagsandnotflags[count]andnotmatchFlag(name,flags)then table.insert(references,reference) else text=removeString(text,reference) ifnamethen forcitationinstring.gmatch(text,'<%s*[Rr][Ee][Ff][^>]*name%s*=%s*["\']?'..escapeString(name)..'["\']?[^/>]*/%s*>')do text=removeString(text,citation) end end end end returnreferences,text end -- Get the lead section from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Wikitext of the lead section. localfunctiongetLead(text) text=string.gsub('\n'..text,'\n==.*','') text=mw.text.trim(text) ifnottextthenreturnthrowError('lead-empty')end returntext end -- Get the requested sections from the given wikitext. -- @param text Required. Wikitext to parse. -- @param flags Range of sections to return, for example 2 or '1,3-5'. Omit to return all sections. -- @return Sequence of strings containing the wikitext of the requested sections. -- @return Original wikitext minus requested sections. localfunctiongetSections(text,flags) localsections={} localflags,blacklist=parseFlags(flags) localcount=0 localprefix,section,suffix fortitleinstring.gmatch('\n'..text..'\n==','\n==+%s*([^=]+)%s*==+')do count=count+1 prefix,section,suffix=string.match('\n'..text..'\n==','\n()==+%s*'..escapeString(title)..'%s*==+(.-)()\n==') ifnotblacklistand(notflagsorflags[count]ormatchFlag(title,flags)) orblacklistandflagsandnotflags[count]andnotmatchFlag(title,flags)then sections[title]=section else text=string.sub(text,1,prefix)..string.sub(text,suffix) text=string.gsub(text,'\n?==$','')-- remove the trailing \n== end end returnsections,text end -- Get the requested section or <section> tag from the given wikitext (including subsections). -- @param text Required. Wikitext to parse. -- @param section Required. Title of the section to get (in wikitext), for example 'History' or 'History of [[Athens]]'. -- @return Wikitext of the requested section. localfunctiongetSection(text,section) section=mw.text.trim(section) localescapedSection=escapeString(section) -- First check if the section title matches a <section> tag ifstring.find(text,'<%s*[Ss]ection%s+begin%s*=%s*["\']?%s*'..escapedSection..'%s*["\']?%s*/>')then-- avoid expensive search if possible text=mw.text.trim((text :gsub('<%s*[Ss]ection%s+end=%s*["\']?%s*'..escapedSection..'%s*["\']?%s*/>.-<%s*[Ss]ection%s+begin%s*=%s*["\']?%s*'..escapedSection..'%s*["\']?%s*/>','')-- remove text between section tags :gsub('^.-<%s*[Ss]ection%s+begin%s*=%s*["\']?%s*'..escapedSection..'%s*["\']?%s*/>','')-- remove text before first section tag :gsub('<%s*[Ss]ection%s+end=%s*["\']?%s*'..escapedSection..'%s*["\']?%s*/>.*','')-- remove text after last section tag )) iftext==''thenreturnthrowError('section-tag-empty',section)end returntext end locallevel,text=string.match('\n'..text..'\n','\n(==+)%s*'..escapedSection..'%s*==.-\n(.*)') ifnottextthenreturnthrowError('section-not-found',section)end localnextSection='\n=='..string.rep('=?',#level-2)..'[^=].*' text=string.gsub(text,nextSection,'')-- remove later sections with headings at this level or higher text=mw.text.trim(text) iftext==''thenreturnthrowError('section-empty',section)end returntext end -- Replace the first call to each reference defined outside of the text for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> localfunctionfixReferences(text,page,full) ifnotfullthenfull=getText(page)end localrefNames={} localrefName localrefBody localposition=1 whileposition<mw.ustring.len(text)do refName,position=mw.ustring.match(text,'<%s*[Rr][Ee][Ff][^>]*name%s*=%s*["\']?([^"\'>]+)["\']?[^>]*/%s*>()',position) ifrefNamethen refName=mw.text.trim(refName) ifnotrefNames[refName]then-- make sure we process each ref name only once table.insert(refNames,refName) refName=escapeString(refName) refBody=mw.ustring.match(text,'<%s*[Rr][Ee][Ff][^>]*name%s*=%s*["\']?%s*'..refName..'%s*["\']?[^>/]*>.-<%s*/%s*[Rr][Ee][Ff]%s*>') ifnotrefBodythen-- the ref body is not in the excerpt refBody=mw.ustring.match(full,'<%s*[Rr][Ee][Ff][^>]*name%s*=%s*["\']?%s*'..refName..'%s*["\']?[^/>]*>.-<%s*/%s*[Rr][Ee][Ff]%s*>') ifrefBodythen-- the ref body was found elsewhere text=mw.ustring.gsub(text,'<%s*[Rr][Ee][Ff][^>]*name%s*=%s*["\']?%s*'..refName..'%s*["\']?[^>]*/?%s*>',mw.ustring.gsub(refBody,'%%','%%%%'),1) end end end else position=mw.ustring.len(text) end end page=string.gsub(page,'"','')-- remove any quotation marks from the page title text=mw.ustring.gsub(text,'<%s*[Rr][Ee][Ff][^>]*name%s*=%s*["\']?([^"\'>/]+)["\']?[^>/]*(/?)%s*>','<ref name="'..page..' %1"%2>') text=mw.ustring.gsub(text,'<%s*[Rr][Ee][Ff]%s*group%s*=%s*["\']?[^"\'>/]+["\']%s*>','<ref>') returntext end -- Replace the bold title or synonym near the start of the page by a link to the page localfunctionlinkBold(text,page) locallang=mw.language.getContentLanguage() localposition=mw.ustring.find(text,"'''"..lang:ucfirst(page).."'''",1,true)-- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) ormw.ustring.find(text,"'''"..lang:lcfirst(page).."'''",1,true)-- plain search: special characters in page represent themselves ifpositionthen locallength=mw.ustring.len(page) text=mw.ustring.sub(text,1,position+2).."[["..mw.ustring.sub(text,position+3,position+length+2).."]]"..mw.ustring.sub(text,position+length+3,-1)-- link it else-- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text=mw.ustring.gsub(text,"()'''(.-'*)'''",function(a,b) ifnotmw.ustring.find(b,"%[")andnotmw.ustring.find(b,"%{")then-- if not wikilinked or some weird template return"'''[["..page.."|"..b.."]]'''"-- replace '''Foo''' by '''[[page|Foo]]''' else returnnil-- instruct gsub to make no change end end,1)-- "end" here terminates the anonymous replacement function(a, b) passed to gsub end returntext end -- Remove non-free files. -- @param text Required. Wikitext to clean. -- @return Clean wikitext. localfunctionremoveNonFreeFiles(text) localfileNamespaces=getNamespaces('File') localfileName localfileDescription localframe=mw.getCurrentFrame() forfileinstring.gmatch(text,'%b[]')do ifmatchAnyLink(file,fileNamespaces)then fileName='File:'..string.match(file,'%[%[[^:]-:([^]|]+)') fileDescription,fileName=getText(fileName) iffileNamethen ifnotfileDescriptionorfileDescription==''then fileDescription=frame:preprocess('{{'..fileName..'}}')-- try Commons end iffileDescriptionandstring.match(fileDescription,'[Nn]on%-free')then text=removeString(text,file) end end end end returntext end -- Remove any self links localfunctionremoveSelfLinks(text) locallang=mw.language.getContentLanguage() localpage=escapeString(mw.title.getCurrentTitle().prefixedText) localucpage=lang:ucfirst(page) locallcpage=lang:lcfirst(page) text=text :gsub('%[%[('..ucpage..')%]%]','%1') :gsub('%[%[('..lcpage..')%]%]','%1') :gsub('%[%['..ucpage..'|([^]]+)%]%]','%1') :gsub('%[%['..lcpage..'|([^]]+)%]%]','%1') returntext end -- Remove all wikilinks localfunctionremoveLinks(text) text=text :gsub('%[%[[^%]|]+|([^]]+)%]%]','%1') :gsub('%[%[([^]]+)%]%]','%1') :gsub('%[[^ ]+ ([^]]+)%]','%1') :gsub('%[([^]]+)%]','%1') returntext end -- Remove HTML comments localfunctionremoveComments(text) text=string.gsub(text,'<!%-%-.-%-%->','') returntext end -- Remove behavior switches, such as __NOTOC__ localfunctionremoveBehaviorSwitches(text) text=string.gsub(text,'__[A-Z]+__','') returntext end -- Remove bold text localfunctionremoveBold(text) text=string.gsub(text,"'''",'') returntext end -- Main function for modules localfunctionget(page,options) ifnotoptionsthenoptions={}end -- Make sure the page exists ifnotpagethenreturnthrowError('no-page')end page=mw.text.trim(page) page=mw.text.decode(page) ifpage==''thenreturnthrowError('no-page')end localpage,hash,section=string.match(page,'([^#]+)(#?)(.*)') localtext,temp=getText(page,options.noFollow) ifnottempthenreturnthrowError('invalid-title',page)end page=temp ifnottextthenreturnthrowError('page-not-found',page)end localfull=text-- save the full text for fixReferences below -- Get the requested section iftruthy(section)then text=getSection(text,section) elseiftruthy(hash)then text=getLead(text) end -- Keep only the requested elements localelements ifoptions.onlythen ifoptions.only=='sections'thenelements=getSections(text,options.sections)end ifoptions.only=='lists'thenelements=getLists(text,options.lists)end ifoptions.only=='files'thenelements=getFiles(text,options.files)end ifoptions.only=='tables'thenelements=getTables(text,options.tables)end ifoptions.only=='templates'thenelements=getTemplates(text,options.templates)end ifoptions.only=='parameters'thenelements=getParameters(text,options.parameters)end ifoptions.only=='paragraphs'thenelements=getParagraphs(text,options.paragraphs)end ifoptions.only=='categories'thenelements=getCategories(text,options.categories)end ifoptions.only=='references'thenelements=getReferences(text,options.references)end text='' ifelementsthen forkey,elementinpairs(elements)do text=text..'\n'..element..'\n' end end end -- Filter the requested elements ifoptions.sectionsandoptions.only~='sections'thenelements,text=getSections(text,options.sections)end ifoptions.listsandoptions.only~='lists'thenelements,text=getLists(text,options.lists)end ifoptions.filesandoptions.only~='files'thenelements,text=getFiles(text,options.files)end ifoptions.tablesandoptions.only~='tables'thenelements,text=getTables(text,options.tables)end ifoptions.templatesandoptions.only~='templates'thenelements,text=getTemplates(text,options.templates)end ifoptions.parametersandoptions.only~='parameters'thenelements,text=getParameters(text,options.parameters)end ifoptions.paragraphsandoptions.only~='paragraphs'thenelements,text=getParagraphs(text,options.paragraphs)end ifoptions.categoriesandoptions.only~='categories'thenelements,text=getCategories(text,options.categories)end ifoptions.referencesandoptions.only~='references'thenelements,text=getReferences(text,options.references)end -- Misc options iftruthy(options.fixReferences)thentext=fixReferences(text,page,full)end iftruthy(options.linkBold)andnottruthy(section)thentext=linkBold(text,page)end iftruthy(options.noBold)thentext=removeBold(text)end iftruthy(options.noLinks)thentext=removeLinks(text)end iftruthy(options.noSelfLinks)thentext=removeSelfLinks(text)end iftruthy(options.noNonFreeFiles)thentext=removeNonFreeFiles(text)end iftruthy(options.noBehaviorSwitches)thentext=removeBehaviorSwitches(text)end iftruthy(options.noComments)thentext=removeComments(text)end -- Remove multiple newlines left over from removing elements text=string.gsub(text,'\n\n\n+','\n\n') text=mw.text.trim(text) returntext end -- Main invocation function for templates localfunctionmain(frame) localargs=parseArgs(frame) localpage=args[1] localok,text=pcall(get,page,args) ifnotokthenreturngetError(text)end localraw=args['raw'] ifrawthenreturntextend returnframe:preprocess(text) end -- Entry points for templates functionp.main(frame)returnmain(frame)end -- Entry points for modules functionp.get(page,options)returnget(page,options)end functionp.getText(page,noFollow)returngetText(page,noFollow)end functionp.getLead(text)returngetLead(text)end functionp.getSection(text,section)returngetSection(text,section)end functionp.getSections(text,flags)returngetSections(text,flags)end functionp.getParagraphs(text,flags)returngetParagraphs(text,flags)end functionp.getParameters(text,flags)returngetParameters(text,flags)end functionp.getCategories(text,flags)returngetCategories(text,flags)end functionp.getReferences(text,flags)returngetReferences(text,flags)end functionp.getTemplates(text,flags)returngetTemplates(text,flags)end functionp.getTables(text,flags)returngetTables(text,flags)end functionp.getLists(text,flags)returngetLists(text,flags)end functionp.getFiles(text,flags)returngetFiles(text,flags)end functionp.getError(message,value)returngetError(message,value)end -- Expose handy methods functionp.truthy(value)returntruthy(value)end functionp.parseArgs(frame)returnparseArgs(frame)end functionp.matchAny(text,pre,list,post,init)returnmatchAny(text,pre,list,post,init)end functionp.matchFlag(value,flags)returnmatchFlag(value,flags)end functionp.getNamespaces(name)returngetNamespaces(name)end functionp.removeBold(text)returnremoveBold(text)end functionp.removeLinks(text)returnremoveLinks(text)end functionp.removeSelfLinks(text)returnremoveSelfLinks(text)end functionp.removeNonFreeFiles(text)returnremoveNonFreeFiles(text)end functionp.removeBehaviorSwitches(text)returnremoveBehaviorSwitches(text)end functionp.removeComments(text)returnremoveComments(text)end returnp