![]() | This module is rated as beta, and is ready for widespread use. It is still new and should be used with some caution to ensure the results are as expected. |
This module helps to enforce MOS:LINKONCE across large sections of text and templates
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 existingWikilinksrealTarget 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 existingWikilinksresolvedTarget then
newContainer#newContainer+1 = wlPiped or wlTarget
else
existingWikilinksrealTarget = true
existingWikilinksresolvedTarget = 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 = openWikilinksi-1
nextLink.Text#nextLink.Text+1 = table.concat(openWikilinksi].Text, "")
end
newText#newText+1 = table.concat(openWikilinks1].Text, "")
end
return table.concat(newText, "")
end
local function main(frame) -- Template entry point
local args = require('Module:Arguments').getArgs(frame)
return linkOnce(args1 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
}
![]() | This module is rated as beta, and is ready for widespread use. It is still new and should be used with some caution to ensure the results are as expected. |
This module helps to enforce MOS:LINKONCE across large sections of text and templates
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 existingWikilinksrealTarget 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 existingWikilinksresolvedTarget then
newContainer#newContainer+1 = wlPiped or wlTarget
else
existingWikilinksrealTarget = true
existingWikilinksresolvedTarget = 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 = openWikilinksi-1
nextLink.Text#nextLink.Text+1 = table.concat(openWikilinksi].Text, "")
end
newText#newText+1 = table.concat(openWikilinks1].Text, "")
end
return table.concat(newText, "")
end
local function main(frame) -- Template entry point
local args = require('Module:Arguments').getArgs(frame)
return linkOnce(args1 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
}