Modul:Vorlage:Anker
Die Dokumentation für dieses Modul kann unter Modul:Vorlage:Anker/Doku erstellt werden
local Anchors = { suite = "Anchors",
serial = "2020-04-10",
item = 79414611,
globals = { Multilingual = 47541920,
TemplUtl = 52364930 }
}
--[=[
Support Template:Anchor {{Anker}}
]=]
local Failsafe = Anchors
local GlobalMod = Anchors
local Config = {
badPattern = "[#'\"%[%]<>|]",
globalPage = { "antispam-container",
"bodyContent",
"catlinks",
"centralNotice",
"content",
"contentSub",
"contentSub2",
"editform",
"editpage-copywarn",
"firstHeading",
"footer",
"fundraising",
"jump-to-nav",
"language-settings-dialog",
"languagesettings-panels",
"page-actions",
"siteNotice",
"siteSub",
"toc",
"toctitle",
"top",
"wikiPreview" },
globalPatterns = { "^mw%-[%l%-]+$",
"^ca%-[%l%-]+$",
"^n%-[%l%-]+$",
"^p%-%a%a[%a%-]+$",
"^pt%-[%l%-]+$",
"^wp%u" },
errCat = false,
errClass = "error_fragment",
errHide = true,
errNS = false,
errInvalid = { en = "Invalid:",
de = "Ungültig:" },
errModule = { en = "Library module missing:",
de = "Bibliotheksmodul fehlt:" },
errNoFragments = { en = "No fragments given",
de = "Keine Bezeichner angegeben" },
errUnkown = { en = "Unkown parameter:",
de = "Parameter unbekannt:" }
}
local foreignModule = function ( access, advanced, append, alt, alert )
-- Fetch global module
-- Precondition:
-- access -- string, with name of base module
-- advanced -- true, for require(); else mw.loadData()
-- append -- string, with subpage part, if any; or false
-- alt -- number, of wikidata item of root; or false
-- alert -- true, for throwing error on data problem
-- Postcondition:
-- Returns whatever, probably table
-- 2020-01-01
local storage = access
local finer = function ()
if append then
storage = string.format( "%s/%s",
storage,
append )
end
end
local fun, lucky, r, suited
if advanced then
fun = require
else
fun = mw.loadData
end
GlobalMod.globalModules = GlobalMod.globalModules or { }
suited = GlobalMod.globalModules[ access ]
if not suited then
finer()
lucky, r = pcall( fun, "Module:" .. storage )
end
if not lucky then
if not suited and
type( alt ) == "number" and
alt > 0 then
suited = string.format( "Q%d", alt )
suited = mw.wikibase.getSitelink( suited )
GlobalMod.globalModules[ access ] = suited or true
end
if type( suited ) == "string" then
storage = suited
finer()
lucky, r = pcall( fun, storage )
end
if not lucky and alert then
error( "Missing or invalid page: " .. storage )
end
end
return r
end -- foreignModule()
local function factory( apply )
-- Localization of messages
-- apply -- string, with message key
-- Returns message text; at least English
-- TODO: Might be extended by tabData and Multilingual
local entry = Config[ apply ]
local r
if entry then
-- TODO: page language
r = entry[ mw.language.getContentLanguage():getCode() ]
if not r then
r = entry.en
end
else
r = tostring( mw.html.create( "span" )
:addClass( "error" )
:wikitext( string.format( "????.%s.????",
apply ) ) )
end
return r
end -- factory()
local function faculty( adjust )
-- Test template arg for boolean
-- adjust -- string or nil
-- Returns boolean
local r
if type( adjust ) == "string" then
r = mw.text.trim( adjust )
if r ~= "" and r ~= "0" then
r = true
end
else
r = adjust or false
end
return r
end -- faculty()
local function fair( assembly )
-- Create HTML code for anchors
-- assembly -- sequence table, with identifiers
-- Returns HTML string
local collection = mw.html.create( "" )
local f = function ( a )
collection:node( mw.html.create( "span" )
:attr( "id", a ) )
end
local s, seen, shift
table.sort( assembly )
for i = 1, #assembly do
s = assembly[ i ]
if s ~= seen then
if not s:match( "^[%w_]+$" ) then
shift = mw.uri.encode( s, "WIKI" )
if shift ~= s then
f( shift:gsub( "%%", "." ) )
end
end
f( s )
seen = s
end
end -- i = 1, #assembly
return tostring( collection )
end -- fair()
local function fault( alert, about )
-- Format message with class="error" or similar
-- alert -- string, with message key
-- about -- string, with explanation
-- Returns message with markup
local scope = Config.errClass
local story = factory( alert )
local TemplUtl = foreignModule( "TemplUtl",
true,
false,
Anchors.globals.TemplUtl )
local r, scope, style
if type( TemplUtl ) == "table" and
type( TemplUtl.TemplUtl ) == "function" then
TemplUtl = TemplUtl.TemplUtl()
else
TemplUtl = false
end
if Config.self then
story = string.format( "%s * %s", Config.self, story )
end
if Config.errClasses then
if scope then
scope = string.format( "%s %s",
scope, Config.errClasses )
else
scope = Config.errClasses
end
end
if about then
story = string.format( "%s %s", story, about )
end
if TemplUtl then
r = TemplUtl.failure( story,
not Config.errHide,
scope,
Config.frame )
else
r = tostring( mw.html.create( "span" )
:addClass( scope )
:addClass( "error" )
:wikitext( story ) )
end
if Config.errCat then
if Config.errNS then
local st = type( Config.errNS )
if st == "number" then
st = { }
table.insert( st, Config.errNS )
Config.errNS = st
elseif st == "string" then
Config.errNS = mw.text.split( Config.errNS, "%s+" )
for i = 1, #Config.errNS do
Config.errNS[ i ] = tonumber( Config.errNS[ i ] )
end -- for i
end
if type( Config.errNS ) == "table" then
local ns = mw.title.getCurrentTitle().namespace
for i = 1, #Config.errNS do
if Config.errNS[ i ] == ns then
Config.errNS = false
break -- for i
end
end -- for i
end
end
if not Config.errNS then
r = string.format( "%s[[Category:%s]]", r, Config.errCat )
end
end
return r
end -- fault()
local function features( adapt )
-- Set parameters on compliance
if type( adapt ) == "table" then
Config.limitMarkup = faculty( adapt.rejectMarkup )
Config.limitMW = faculty( adapt.rejectMW )
Config.limitXML = faculty( adapt.onlyXML )
Config.lightXML = faculty( adapt.softXML )
end
end -- features()
local function main( argsF, argsT )
-- Invocation
-- argsF -- table, with #invoke parameters, or false
-- argsT -- table, with template parameters
-- Returns appropriate string
local r
if type( argsF ) == "table" then
Config.errCat = argsF.errCat
Config.errClasses = argsF.errClasses
Config.errHide = faculty( argsF.errHide )
Config.errNS = argsF.errNS
features( argsF )
if mw.site.server:find( "de.wikipedia.", 1, true ) then
Config.errClasses = "Linkwartung"
Config.errHide = true
Config.errNS = 0
Config.limitMarkup = true
Config.limitMW = false
Config.limitXML = true
Config.lightXML = true
end
end
if type( argsT ) == "table" then
local e, got, less, s, unknown
for k, v in pairs( argsT ) do
if type( k ) == "number" then
v = mw.text.trim( v )
if v == "" then
v = false
end
less = ( k < 0 )
k = false
else
less = k:match( "^[x%-][1-9]%d*" )
if less then
k = false
end
end
if k then
unknown = unknown or { }
table.insert( unknown, k )
elseif v then
got = got or { }
v = v:gsub( " +", "_" )
:gsub( "_+", "_" )
got[ v ] = less
end
end -- for k, v
if unknown then
e = mw.html.create( "code" )
:css( "white-space", "nowrap" )
:wikitext( table.concat( unknown, " " ) )
s = string.format( " in [[%s]]",
Config.frame:getTitle() )
e:tag( "span" )
:wikitext( s )
r = fault( "errUnkown", tostring( e ) )
elseif got then
local profile = { limitMarkup = false,
limitMW = false,
limitXML = false }
local bad, checked, legal, o, s
for k, v in pairs( got ) do
if v then
o = profile
else
o = false
end
s, legal = Anchors.feasible( k, o )
if s then
checked = checked or { }
table.insert( checked, s )
end
if not legal then
bad = bad or { }
e = mw.html.create( "code" )
:css( "white-space", "nowrap" )
:wikitext( mw.text.encode( k ) )
table.insert( bad, tostring( e ) )
end
end -- for k, v
if checked then
r = fair( checked )
end
if bad then
s = string.format( "%s in [[%s]]",
table.concat( bad, ", " ),
Config.frame:getParent():getTitle() )
r = string.format( "%s%s",
r or "",
fault( "errInvalid", s ) )
end
else
r = fault( "errNoFragments" )
end
end
return r or ""
end -- main()
Anchors.feasible = function ( analyse, allow )
-- Check ID for compliance
-- Precondition:
-- analyse -- string, with presumptive anchor
-- allow -- optional table, with compliance criteria
-- -- limitMarkup
-- -- limitMW
-- -- limitXML
-- -- lightXML
-- Postcondition:
-- Returns
-- 1. -- string -- with normalised anchor
-- -- false -- if not appropriate
-- 2. -- true, if fully compliant
local r1 = false
local r2 = false
if type( analyse ) == "string" then
local s = mw.text.trim( analyse )
if s ~= "" then
local deny = { limitMarkup = Config.limitMarkup,
limitMW = Config.limitMW,
limitXML = Config.limitXML,
lightXML = Config.lightXML }
if type( allow ) == "table" then
for k, v in pairs( deny ) do
if type( allow[ k ] ) == "boolean" then
deny[ k ] = allow[ k ]
end
end -- for k, v
end
if deny.limitMW then
r1 = true
else
s = mw.text.unstripNoWiki( s )
s = mw.text.trim( s )
r1 = ( s ~= "" )
end
if r1 and deny.limitMarkup and
( s:match( Config.badPattern ) or
s:match( "&#%w+;" ) ) then
r1 = false
end
if r1 then
for k = 1, #Config.globalPage do
if s == Config.globalPage[ k ] then
r1 = false
break -- k = 1, #Config.globalPage
end
end -- k = 1, #Config.globalPage
if r1 then
Config.badCtrl = Config.badCtrl or
string.format( "[%c-%c]", 1, 31 )
r1 = not s:find( Config.badCtrl )
if r1 and s:match( "%%%x%x" ) then
r1 = false
end
if r1 then
for k = 1, #Config.globalPatterns do
if s:match( Config.globalPatterns[ k ] ) then
r1 = false
break -- k = 1, #Config.globalPatterns
end
end -- k = 1, #Config.globalPatterns
end
end
end
if r1 then
r2 = true
if deny.limitXML and
( s:match( "^%d" ) or
s:sub( 1, 1 ) == "-" ) then
r2 = false
if not deny.lightXML then
r1 = false
end
end
end
if r1 then
r1 = s:gsub( " +", "_" )
:gsub( "_+", "_" )
end
end
end
return r1, r2
end -- Anchors.feasible()
Failsafe.failsafe = function ( atleast )
-- Retrieve versioning and check for compliance
-- Precondition:
-- atleast -- string, with required version or "wikidata" or "~"
-- or false
-- Postcondition:
-- Returns string -- with queried version, also if problem
-- false -- if appropriate
-- 2019-10-15
local last = ( atleast == "~" )
local since = atleast
local r
if last or since == "wikidata" then
local item = Failsafe.item
since = false
if type( item ) == "number" and item > 0 then
local entity = mw.wikibase.getEntity( string.format( "Q%d",
item ) )
if type( entity ) == "table" then
local seek = Failsafe.serialProperty or "P348"
local vsn = entity:formatPropertyValues( seek )
if type( vsn ) == "table" and
type( vsn.value ) == "string" and
vsn.value ~= "" then
if last and vsn.value == Failsafe.serial then
r = false
else
r = vsn.value
end
end
end
end
end
if type( r ) == "nil" then
if not since or since <= Failsafe.serial then
r = Failsafe.serial
else
r = false
end
end
return r
end -- Failsafe.failsafe()
-- Export
local p = { }
p.f = function ( frame )
local lucky, r
Config.frame = frame
lucky, r = pcall( main, frame.args, frame:getParent().args )
if not lucky then
r = tostring( mw.html.create( "span" )
:addClass( "error" )
:wikitext( string.format( "%s * %s",
frame:getTitle(),
r ) ) )
end
return r
end -- p.f()
p.feasible = function ( frame )
local r1, r2
Config.frame = frame
features( frame.args )
r1, r2 = Anchors.feasible( frame.args[ 1 ] )
return r1 or ""
end -- p.feasible()
p.forbidden = function ()
local r = ""
local sep = ""
local e
for k = 1, #Config.globalPage do
e = mw.html.create( "code" )
:css( "white-space", "nowrap" )
:wikitext( Config.globalPage[ k ] )
r = string.format( "%s%s%s", r, sep, tostring( e ) )
sep = ", "
end -- k = 1, #Config.globalPage
return r
end -- p.forbidden()
p.failsafe = function ( frame )
-- Versioning interface
local s = type( frame )
local since
if s == "table" then
since = frame.args[ 1 ]
elseif s == "string" then
since = frame
end
if since then
since = mw.text.trim( since )
if since == "" then
since = false
end
end
return Failsafe.failsafe( since ) or ""
end -- p.failsafe()
p.Anchors = function ()
return Anchors
end -- p.Anchors
return p