Module:Sandbox/Aidan9382/Link once
Jump to navigation
Jump to search
| This module is rated as beta. It is considered ready for widespread use, but as it is still relatively new, it should be applied with some caution to ensure results are as expected. |
This module helps to enforce MOS:LINKONCE across large sections of text and templates
Usage
[edit source]The module takes one primary argument, 1 (the text to be modified), and will delink any duplicate occurrences of wikilinks within the section provided.
require("strict")
local yesno = require("Module:Yesno")
-- Behaviour for these functions determined via [[Help:Pipe trick]]
local function wlPipeTrick(target)
target = target:gsub("^[a-zA-Z0-9 _-]-:(.*)$", "%1") --Remove the namespace
if target:find("%(.+%)$") then --If ending parenthesis
target = target:gsub("^(.-) *%(.+%)$", "%1") --Remove ending parenthesis
else
target = target:gsub("^(.-), .*$", "%1") --Else, remove ending comma
end
return target
end
local function wlReversePipeTrick(target)
local current = mw.title.getCurrentTitle().prefixedText
if current:find("%(.+%)$") then --If ending parenthesis
target = target .. current:gsub("^.-( *%(.+%))$", "%1") --Append ending parenthesis
else
target = target .. current:gsub("^.-(, .*)$", "%1") --Else, append ending comma
end
return target
end
local function getWikilinkInfo(wikilink)
--[=[
Returns the wikilink's target and its display text.
Automatically recreates the effect of any [[Help:pipe tricks|]]
--]=]
local trim = mw.text.trim
local trimmed = string.sub(wikilink, 3, -3)
local firstPipe = string.find(trimmed, "|")
if firstPipe then
local target = string.sub(trimmed, 1, firstPipe-1)
local displayText = string.sub(trimmed, firstPipe+1)
if target == "" then -- [[|XYZ]]
return trim(wlReversePipeTrick(displayText)), trim(displayText)
elseif displayText == "" then -- [[XYZ|]]
return trim(target), trim(wlPipeTrick(target))
else --[[ABC|XYZ]]
return trim(target), trim(displayText)
end
else
local out = trim(trimmed)
if out:find("^/.-/+$") and mw.title.getCurrentTitle().namespace ~= 0 then -- [[/Test/]]
return out, out:gsub("^/(.-)/+$", "%1")
else -- [[Test]]
return out, nil
end
end
end
local function linkOnce(text, options) -- Module entry point
--[=[
We are going to traverse the text linearly ourselves.
Using %b[] isn't preferable as nested brackets (E.g. the wikilink to t
in [[File:x|Cap[[t]]ion]]) would be missed and doing a check for
%[%[.-%]%] wouldn't work for the exact same testcase for other reasons
--]=]
local options = options or {follow_redirects=true}
local newText = {}
local scannerPosition = 1
local existingWikilinks = {}
local openWikilinks = {}
while true do
local Position, _, Character = string.find(text, "([%[%]])%1", scannerPosition)
local container = (openWikilinks[#openWikilinks] or {Text=newText}).Text
if not Position then --Done
container[#container+1] = string.sub(text, scannerPosition)
break
end
container[#container+1] = string.sub(text, scannerPosition, Position-1)
scannerPosition = Position+2 --+2 to pass the [[ / ]]
if Character == "[" then --Add a [[ to the pending wikilink queue
openWikilinks[#openWikilinks+1] = {Position = Position, Text = {"[["}}
else --Pair up the ]] to any available [[
if #openWikilinks >= 1 then
local openingPair = table.remove(openWikilinks) --Pop the latest [[
local wlStart, wlText = openingPair.Position, table.concat(openingPair.Text, "") .. "]]"
local wikilink = string.sub(text, wlStart, Position+1)
local wlTarget, wlPiped = getWikilinkInfo(wikilink)
local newContainer = (openWikilinks[#openWikilinks] or {Text=newText}).Text
if wlTarget:find("^[Ii]mage:") or wlTarget:find("^[Ff]ile:") or wlTarget:find("^[Cc]ategory:") then --Files/Images/Categories aren't processed (they aren't really wikilinks)
newContainer[#newContainer+1] = wlText
else
local realTarget = wlTarget:sub(1, 1):upper() .. wlTarget:sub(2)
if existingWikilinks[realTarget] then
newContainer[#newContainer+1] = wlPiped or wlTarget
else
local resolvedTarget = realTarget
if options.follow_redirects then
local titleObj = mw.title.new(wlTarget)
if titleObj then
local newTarget = titleObj.isRedirect and titleObj.redirectTarget.fullText or titleObj.fullText
resolvedTarget = newTarget:sub(1, 1):upper() .. newTarget:sub(2)
end
end
if existingWikilinks[resolvedTarget] then
newContainer[#newContainer+1] = wlPiped or wlTarget
else
existingWikilinks[realTarget] = true
existingWikilinks[resolvedTarget] = true
newContainer[#newContainer+1] = wlText
end
end
end
else --Just a random ]] with no matching pair, dont process it
newText[#newText+1] = "]]"
end
end
end
if #openWikilinks > 0 then --Random [[ with no matching pair, dont process it
for i = #openWikilinks, 2, -1 do
local nextLink = openWikilinks[i-1]
nextLink.Text[#nextLink.Text+1] = table.concat(openWikilinks[i].Text, "")
end
newText[#newText+1] = table.concat(openWikilinks[1].Text, "")
end
return table.concat(newText, "")
end
local function main(frame) -- Template entry point
local args = require('Module:Arguments').getArgs(frame)
return linkOnce(args[1] or "", {
follow_redirects = yesno(args.follow_redirects) or true,
})
end
return {
-- Main entry points
main = main,
linkOnce = linkOnce,
-- Helper functions
wlPipeTrick = wlPipeTrick,
wlReversePipeTrick = wlReversePipeTrick,
getWikilinkInfo = getWikilinkInfo
}