Module:Redirect: Difference between revisions
From Neodyland Wiki
More actions
better performing like this |
m 34 revisions imported |
||
| (11 intermediate revisions by 4 users not shown) | |||
| Line 27: | Line 27: | ||
function p.getTargetFromText(text) | function p.getTargetFromText(text) | ||
text = text or '' | text = text or '' | ||
return text:match('#[Rr][Ee][Dd] | return text:match('^#[Rr][Ee][Dd][Ii][Rr][Ee][Cc][Tt][%s:]*%[%[[%s_:]*([^]|]-)[%s_]*%]%]') | ||
end | end | ||
| Line 45: | Line 45: | ||
{{Seecat|target}} | {{Seecat|target}} | ||
{{See cat|target}} | {{See cat|target}} | ||
{{Endashcatredirect|target}} | |||
{{Synonym taxon category redirect|target}} | |||
{{Invalid taxon category redirect|target}} | |||
{{Monotypic taxon category redirect|target}} | |||
The `target` value may be prefixed by 'Category:' or not (implied). For the full list of aliases, see | The `target` value may be prefixed by 'Category:' or not (implied). For the full list of aliases, see | ||
https://commons.wikimedia.org/w/index.php?title=Special:WhatLinksHere/Template:Category_redirect&hidetrans=1&hidelinks=1 | https://commons.wikimedia.org/w/index.php?title=Special:WhatLinksHere/Template:Category_redirect&hidetrans=1&hidelinks=1 | ||
| Line 51: | Line 55: | ||
-- Basic filtering: only in the relevant content shown on target page itself | -- Basic filtering: only in the relevant content shown on target page itself | ||
content = (content or '') | content = (content or '') | ||
:gsub('<!%-%-(.-)%-%->', '') -- | :gsub('<!%-%-(.-)%-%->', '') -- Discard HTML comments. | ||
:gsub('<includeonly%s*>(.-)</includeonly>', '') -- | :gsub('<includeonly%s*>(.-)</includeonly>', '') -- Discard 'includeonly' sections and their content. | ||
:gsub('<includeonly%s*>(.-)$', '') -- | :gsub('<includeonly%s*>(.-)$', '') -- Discard unclosed 'includeonly' sections. | ||
:gsub('<[/]?onlyinclude>', '') -- | :gsub('<[/]?onlyinclude%s*>', '') -- Discard 'onlyinclude' opening/closing tags. | ||
:gsub('<[/]?noinclude>', '') -- | :gsub('<[/]?noinclude%s*>', '') -- Discard 'noinclude' opening/closing tags. | ||
:gsub('</?nowiki%s*/?>', '') -- | :gsub('</?nowiki%s*/?>', '') -- Discard nowiki opening/closing/selfclosed tags. | ||
-- Locate the template transclusion, keep their parameters only | -- Locate the template transclusion, keep their parameters only. | ||
-- Note: there may potentially be several instances, this should not occur | -- Note: there may potentially be several instances, this should not occur. | ||
content = | content = | ||
content:match('{{[%s_]*[Cc]ategory*[%s_]*[Rr]edirect[%s_]*|(.-)}}') or | content:match('^#[Rr][Ee][Dd][Ii][Rr][Ee][Cc][Tt][%s:]*%[%[[%s_:]*([^]|]-)[%s_]*%]%]') or | ||
content:match('{{[%s_]*[Cc]at[%s_]*[Rr]edirect[%s_]*|(.-)}}') or | content:match('{{[%s_]*[Cc]ategory*[%s_]*[Rr]edirect[%s_]*|[%s_:]*(.-)[%s_]*}}') or | ||
content:match('{{[%s_]*[Cc]at-[Rr]edirect[%s_]*|(.-)}}') or | content:match('{{[%s_]*[Cc]at[%s_]*[Rr]edirect[%s_]*|[%s_:]*(.-)[%s_]*}}') or | ||
content:match('{{[%s_]*[Cc]at[%s_]*[Rr]edir[%s_]*|(.-)}}') or | content:match('{{[%s_]*[Cc]at-[Rr]edirect[%s_]*|[%s_:]*(.-)[%s_]*}}') or | ||
content:match('{{[%s_]*[Cc]at-red[%s_]*|(.-)}}') or | content:match('{{[%s_]*[Cc]at[%s_]*[Rr]edir[%s_]*|[%s_:]*(.-)[%s_]*}}') or | ||
content:match('{{[%s_]*[Rr]edirect[%s_]*[Cc]ategory[%s_]*|(.-)}}') or | content:match('{{[%s_]*[Cc]at-red[%s_]*|[%s_:]*(.-)[%s_]*}}') or | ||
content:match('{{[%s_]*[Rr]edirect[%s_]*[Cc]at[%s_]*|(.-)}}') or | content:match('{{[%s_]*[Rr]edirect[%s_]*[Cc]ategory[%s_]*|[%s_:]*(.-)[%s_]*}}') or | ||
content:match('{{[%s_]*[Ss]ee[%s_]*[Cc]at[%s_]*|(.-)}}') | content:match('{{[%s_]*[Rr]edirect[%s_]*[Cc]at[%s_]*|[%s_:]*(.-)[%s_]*}}') or | ||
content:match('{{[%s_]*[Ss]ee[%s_]*[Cc]at[%s_]*|[%s_:]*(.-)[%s_]*}}') or | |||
content:match('{{[%s_]*[Ee]ndashcatredirect[%s_]*|[%s_:]*(.-)[%s_]*}}') or | |||
content:match('{{[%s_]*[Ss]ynonym[%s_]*taxon[%s_]*category[%s_]*redirect[%s_]*|[%s_:]*(.-)[%s_]*}}') or | |||
content:match('{{[%s_]*[Ii]nvalid[%s_]*taxon[%s_]*category[%s_]*redirect[%s_]*|[%s_:]*(.-)[%s_]*}}') or | |||
content:match('{{[%s_]*[Mm]onotypic[%s_]*taxon[%s_]*category[%s_]*redirect[%s_]*|[%s_:]*(.-)[%s_]*}}') | |||
if not content then | if not content then | ||
return nil | return nil | ||
end | end | ||
do | do | ||
-- Parse template parameters (they may be in arbitrary order) | -- Parse template parameters (they may be in arbitrary order). | ||
local params, n = {}, 0 | local params, n = {}, 0 | ||
for param in content:gmatch( | for param in content:gmatch('([^|]+)') do | ||
local key | local key | ||
local pos = param:find('=') | local pos = param:find('=') | ||
| Line 86: | Line 95: | ||
params[key] = param | params[key] = param | ||
end | end | ||
-- The target is in parameter ['1'] of the template transclusion | -- The target is in parameter ['1'] of the template transclusion. | ||
content = params['1'] | content = params['1'] | ||
end | end | ||
-- Check there's an effective target parameter to the template | -- Check there's an effective target parameter to the template. | ||
if not content then | if not content then | ||
return nil | return nil | ||
| Line 95: | Line 104: | ||
content = content | content = content | ||
-- The target parameter may contain some known character entities | -- The target parameter may contain some known character entities | ||
-- (their validity in page names is not checked here, just parsed) | -- (their validity in page names is not checked here, just parsed). | ||
:gsub('&([#%d%a]+);', function(entity) | :gsub('&([#%d%a]+);', function(entity) | ||
if entity:sub(1,1) == '#' then | if entity:sub(1,1) == '#' then | ||
| Line 105: | Line 114: | ||
end | end | ||
if codepoint and ( | if codepoint and ( | ||
codepoint >= 0x0009 and codepoint <= 0x000D or -- | codepoint >= 0x0009 and codepoint <= 0x000D or -- Accept some whitespace C0 controls. | ||
codepoint >= 0x0020 and codepoint <= 0x10FFFD -- | codepoint >= 0x0020 and codepoint <= 0x10FFFD -- Normal range. | ||
and (codepoint < 0x0080 or codepoint > 0x009F) -- | and (codepoint < 0x0080 or codepoint > 0x009F) -- Exclude C1 controls. | ||
and (codepoint < 0xDC00 or codepoint > 0xDFFF) -- | and (codepoint < 0xDC00 or codepoint > 0xDFFF) -- Exclude surrogates. | ||
and (codepoint < 0xFDD0 or codepoint > 0xFDEF) -- | and (codepoint < 0xFDD0 or codepoint > 0xFDEF) -- Exclude non-characters in BMP. | ||
and codepoint % 0x10000 <= 0xFFFD -- | and codepoint % 0x10000 <= 0xFFFD -- Exclude non-characters at end of planes. | ||
) then | ) then | ||
return char(codepoint) | return char(codepoint) | ||
| Line 122: | Line 131: | ||
return entity | return entity | ||
end) | end) | ||
-- Normalize spaces in the target according to Mediawiki pagename rules | -- Normalize spaces in the target according to Mediawiki pagename rules. | ||
:gsub('[%s_]+', ' ') | :gsub('[%s_]+', ' ') | ||
-- The target parameter should be trimmed by the template | -- The target parameter should be trimmed by the template. | ||
:match('^ ?(.-) ?$') | :match('^ ?(.-) ?$') | ||
-- The target should not be empty and not contain tag delimiters or other character forbidden in full page names | -- The target should not be empty and not contain tag delimiters or other character forbidden in full page names. | ||
-- Note: templates used inside the target parameter of the soft redirect are not expanded (there should be none) | -- Note: templates used inside the target parameter of the soft redirect are not expanded (there should be none), | ||
-- as this is costly or memory intensive and slow (would require invoking a full mediawiki parser). | -- as this is costly or memory intensive and slow (would require invoking a full mediawiki parser). | ||
if content == '' or content:find('[<>%[%]{|}]') then | if content == '' or content:find('[<>%[%]{|}]') then | ||
| Line 140: | Line 149: | ||
-- Get page name that a redirect leads to, or nil if it isn't a redirect. | -- Get page name that a redirect leads to, or nil if it isn't a redirect. | ||
function p.getTargetFrom(obj) | -- fulltext is an optional option: when set, it indicates if we want the `.fullText`, otherwise we get the `.prefixedText`. | ||
function p.getTargetFrom(obj, fulltext) | |||
--[[ | --[[ | ||
obj:getContent() is intensive for large pages, but obj.redirectTarget returns directly | obj:getContent() is intensive for large pages, but obj.redirectTarget returns directly | ||
| Line 158: | Line 168: | ||
if r then | if r then | ||
local obj2 = getTitle(r) | local obj2 = getTitle(r) | ||
if obj2 then -- getTitle | if obj2 then -- Detect when getTitle fails. | ||
obj = obj2 -- | obj = obj2 -- True replacement with the target. | ||
-- | -- Special: the replacement may also be a hard redirect. | ||
r = obj.isRedirect and obj.redirectTarget | r = obj.isRedirect and obj.redirectTarget | ||
-- | -- Not done here: second soft redirect detection. | ||
end | end | ||
end | end | ||
| Line 168: | Line 178: | ||
if not r and obj.isRedirect then | if not r and obj.isRedirect then | ||
-- The page is a redirect, but matching failed, which may be a bug | -- The page is a redirect, but matching failed, which may be a bug | ||
-- in redirect matching pattern, so throw an error. | -- in the redirect matching pattern, so throw an error. | ||
error(string.format( | error(string.format( | ||
'could not parse redirect on page "%s"', | 'could not parse redirect on page "%s"', | ||
| Line 174: | Line 184: | ||
)) | )) | ||
end | end | ||
r = | r = fulltext and obj.fullText or obj.prefixedText | ||
end | end | ||
return r | return r | ||
| Line 186: | Line 196: | ||
titleObj = target | titleObj = target | ||
else | else | ||
error(string.format( | error(string.format( | ||
'bad argument #1 to "fmtTitle": string, number, or title object expected, got %s', | |||
type(target) | type(target) | ||
), 2) | ), 2) | ||
| Line 209: | Line 219: | ||
titleObj = page | titleObj = page | ||
else | else | ||
error(string.format( | error(string.format( | ||
'bad argument #1 to "getTarget": string, number, or title object expected, got %s', | |||
type(page) | type(page) | ||
), 2) | ), 2) | ||
end | end | ||
if titleObj and titleObj.exists and | if titleObj and titleObj.exists and | ||
(titleObj.isRedirect or titleObj:inNamespace( | (titleObj.isRedirect or titleObj:inNamespace('Category')) | ||
then | then | ||
-- Find the target by using string matching on the page content. | -- Find the target by using string matching on the page content. | ||
return fmtTitle(p.getTargetFrom(titleObj), fulltext, ensureTitleExists) | return fmtTitle(p.getTargetFrom(titleObj, fulltext), fulltext, ensureTitleExists) | ||
end | end | ||
end | end | ||
| Line 232: | Line 242: | ||
--]] | --]] | ||
function p.luaMain(rname, bracket, fulltext, ensureTitleExists) | function p.luaMain(rname, bracket, fulltext, ensureTitleExists) | ||
if type(rname) ~= | if type(rname) ~= 'string' or not rname:find('%S') then | ||
return nil | return nil | ||
end | end | ||
rname = rname:match( | rname = rname:match('%[%[[%s_:]*([^%]|]-)[%s_]*%]%]') | ||
or rname:match('^[%s_:]*([%]|]-)[^%s_]*$') | |||
or rname | |||
local ret = p.getTarget(rname, fulltext, ensureTitleExists) or | local ret = p.getTarget(rname, fulltext, ensureTitleExists) or | ||
fmtTitle(rname, fulltext, ensureTitleExists) | fmtTitle(rname, fulltext, ensureTitleExists) | ||
link = bracket and (ret == rname and | local link = bracket and (ret == rname and '[[:%s]]' or '[[:%s|%s]]') or '%s' | ||
return ret and link:format(ret, rname) | return ret and link:format(ret, rname) | ||
end | end | ||