Модуль:Disambig
Перейти к навигации
Перейти к поиску
Внимание! Это один из самых используемых модулей. |
Этот модуль использует TemplateStyles и загружает следующие таблицы CSS-стилей:
|
На данный момент реализует вывод шаблона {{Неоднозначность}} и часть вывода {{Категория-неоднозначность}}. Со временем будет дописан, чтобы выполнять разного рода проверки на страницах значений.
Код вызова модуля:
{{#invoke:Disambig|main}}
Данные
Вызывает и обрабатывает Module:Disambig/data.json для данных о разных типах значений. Все доступные типы (кроме служебных) можно увидеть на Шаблон:Неоднозначность#Поддерживаемые типы. Данные на странице данных представлены в следующем виде:
"ключ": {
"aliases": [
"алиас 1",
"алиас 2",
"алиас_3"
],
"image": "иконка",
"desc": "описание формата «Список статей о X»",
"short": "необязательно: краткое описание того же формата",
"about": "уточнение формата «о конкретном X»",
"seeAlsoCategory": "для персоналий: категория со списком всех статей с названием",
"category": "подкатегория формата «Страницы значений:X»",
"categorySortPrefixes": [
"префиксы в названии через запятую, которые следует поставить в конец при сортировке в категориях"
]
}
По возможности следует добавлять минимальное число алиасов (желательно 0).
Минимальный пустой шаблон для вставки нового типа на страницу:
"ключ": {
"image": "",
"desc": "",
"about": "",
"category": ""
}
Новые типы должны обсуждаться на Обсуждение проекта:Страницы значений перед добавлением.
Функции
p.main
/p._main
— вызов шаблона {{Неоднозначность}}.p.alias
— вызов шаблона {{Неоднозначность}} из шаблонов-обёрток для их корректной подстановки и учёта во включениях основного шаблона.p.category
/p._category
— генерация списка категорий в шаблоне {{Категория-неоднозначность}}.p.doc
/p._doc
— генерация автоматической таблицы документации известных типов значений на странице Шаблон:Неоднозначность/doc.p.templateData
/p._templateData
— добавление известных типов значений в блок TemplateData на странице документации.
require( 'strict' )
local p = {}
local getArgs = require( 'Module:Arguments' ).getArgs
local escapePattern = require( 'Module:String' )._escapePattern
local nestedQuotes
local currentTitle = mw.title.getCurrentTitle()
-- [[Module:Disambig/styles.css]]
local templateStylesPage = 'Module:Disambig/styles.css'
local data = mw.loadJsonData( 'Module:Disambig/data.json' )
local configData = data[ '--config' ]
local defaultData = data[ '--disambig' ]
local errorData = data[ '--error' ]
local function getAliases()
local result = {}
for key, val in pairs( data ) do
if val[ 'aliases' ] then
for i, alias in ipairs( val[ 'aliases' ] ) do
result[ alias ] = key
end
end
end
return result
end
local aliases = getAliases()
local function isEmpty( str )
return str == nil or str == ''
end
local function escapeRepl( text )
-- Replace % to %% to avoid %1, %2, %3 being treated as groups
return ( mw.ustring.gsub( text, '%%', '%%%' ) )
end
local function getConfig( key, isRequired )
if configData == nil then
return error( 'getConfig: config is missing in Module:Disambig/data.json' )
end
if isRequired ~= false and isEmpty( configData[ key ] ) then
return error( string.format( 'getConfig: "%s" key is missing in Module:Disambig/data.json' ) )
end
return configData[ key ] or nil
end
local function isNotOther( dtype )
local aliasesOther = data[ '--other' ][ 'aliases' ]
for i, alias in ipairs( aliasesOther ) do
if dtype == alias then
return false
end
end
return true
end
local function pageNameBase( page )
return ( mw.ustring.gsub( page, '^%s*(.+)%s+%b()%s*$', '%1' ) )
end
local function setCategory( title, key )
if isEmpty( title ) then
return ''
end
if not isEmpty( key ) then
title = title .. '|' .. key
end
return string.format( '[[Category:%s]]', title )
end
local function formatText( str, params, frame )
if isEmpty( str ) then
return ''
end
-- Replace spaces after specified patterns to non-breaking spaces
local nbspPatterns = getConfig( 'nbspPatterns', false )
if type( nbspPatterns ) ~= 'table' then
nbspPatterns = {}
end
for i, val in ipairs( nbspPatterns ) do
-- last symbol is Unicode to allow replacements near itself
str = mw.ustring.gsub( str, '(%s)(' .. val .. ') ', '%1%2 ' )
end
-- Replace provided parameters in form of {param}
if params ~= nil and type( params ) == 'table' then
for key, val in pairs( params ) do
str = mw.ustring.gsub( str, '%{' .. key .. '%}', escapeRepl( val ) )
end
end
-- Preprocess if frame was provided
if frame ~= nil then
return frame:preprocess( str )
end
return str
end
local function formatNestedQuotes( str )
if str:find( '«' ) ~= nil then
nestedQuotes = nestedQuotes or require( 'Модуль:Вложенные кавычки' )._main
return nestedQuotes( str )
end
return str
end
local function getFixMessage( about )
local aboutStr = getConfig( 'aboutDefault' )
if not isEmpty( about ) then
aboutStr = formatText( getConfig( 'about' ), {
about = about,
} )
end
local page = currentTitle.fullText
local wlh = mw.title.new( 'Special:WhatLinksHere/' .. page )
return formatText( getConfig( 'fixLinks' ), {
about = aboutStr,
url = wlh:fullUrl( 'namespace=0&hidetrans=1' ),
} )
end
local function getPseudoBlock( text, class )
local result = mw.html.create( 'div' )
result:addClass( 'ts-disambig-block ' .. ( class or '' ) )
result:tag( 'div' )
:addClass( 'ts-disambig-image' )
result:tag( 'div' )
:addClass( 'ts-disambig-text' )
:wikitext( text )
return result
end
local function getProjectLink( node )
if node == nil then
return error( 'getProjectLink: no node' )
end
local projectPage = getConfig( 'projectPage', false )
local projectIcon = getConfig( 'projectIcon', false )
local projectLabel = getConfig( 'projectLabel', false )
if isEmpty( projectPage ) or isEmpty( projectIcon ) or isEmpty( projectLabel ) then
return
end
local projTitle = mw.title.new( projectPage )
node:tag( 'div' )
:addClass( 'ts-disambig-projectLink plainlinks group-user-show' )
:tag( 'span' )
:wikitext( string.format(
'[[File:%s|20x20px|link=|alt=]][%s %s]',
projectIcon,
projTitle:fullUrl(),
projectLabel
) )
end
local seeAlsoLinks = {}
local function getSeeAlso( category, isSingle )
local page = pageNameBase( currentTitle.fullText )
local seeAlso = formatText( getConfig( 'seeAlso' ) )
-- Get search link with intitle: with titles with words
local intitleLink = '.'
if isSingle and mw.ustring.find( page, '%a' ) ~= nil then
local pageParts = mw.text.split( mw.ustring.gsub( page, '[,«»?!—%%]', '' ), ' ' )
if not isEmpty( pageParts[ 1 ] ) then
local intitle = mw.uri.encode( 'intitle:"' .. table.concat( pageParts, '" intitle:"' ) .. '"' )
local intitleLabel = formatText( getConfig( 'seeAlsoIntitle' ) )
intitleLink = string.format(
', [%s %s].',
mw.title.new( 'Special:Search' ):fullUrl(
string.format( 'search=%s&sort=create_timestamp_desc', intitle )
),
intitleLabel
)
end
end
if not isEmpty( category ) then
if seeAlsoLinks[ category ] == true then
return ''
end
seeAlsoLinks[ category ] = true
local cat = mw.title.new( 'Category:' .. category )
local catLabel = formatText( getConfig( 'seeAlsoCategory' ) )
return string.format(
'%s [%s <span title="%s">%s</span>]',
seeAlso,
cat:fullUrl( 'from=' .. mw.uri.encode( page ) ),
cat.fullText,
catLabel
) .. intitleLink
end
local pageText = formatNestedQuotes( page )
local prefixIndexLabel = formatText( getConfig( 'seeAlsoPrefixIndex' ), {
prefix = pageText,
} )
return string.format(
'%s [[Special:PrefixIndex/%s|%s]]',
seeAlso,
page,
prefixIndexLabel
) .. intitleLink
end
local function getWrapper( node )
return node:tag( 'div' )
:attr( 'id', 'disambig' )
:attr( 'role', 'note' )
:addClass( 'ts-disambig-mbox metadata plainlinks' )
end
-- Renders a single disambiguation type message
function p._renderPart( frame, data, isSingle, dtype )
if frame == nil then
frame = mw.getCurrentFrame()
end
if type( data ) ~= 'table' or isEmpty( data[ 'desc' ] ) or isEmpty( data[ 'image' ] ) then
return p._renderPart( frame, errorData, isSingle, dtype )
end
local node = mw.html.create( '' )
local result = node:tag( isSingle and 'div' or 'li' )
result:addClass( 'ts-disambig-block' )
local imageSize = isSingle and '30x40px' or '20x20px'
result:tag( 'div' )
:addClass( 'ts-disambig-image' )
:wikitext(
string.format( '[[File:%s|%s|class=noresize|link=|alt=]]', data.image, imageSize )
)
local seeAlsoCat = data[ 'seeAlsoCategory' ]
local text = formatText( data[ 'desc' ] )
local textDiv = result:tag( 'div' )
:addClass( 'ts-disambig-text' )
if isSingle then
if text:find( 'class="error"' ) ~= nil then
text = formatText( text, {
type = dtype,
}, frame )
else
local fullStop = text:find( '%.$' ) == nil and '.' or ''
text = string.format( '[[%s|%s%s]]', getConfig( 'helpPage' ), text, fullStop )
end
textDiv:tag( 'div' ):wikitext( text )
if dtype == '--category' then
textDiv:tag( 'div' ):wikitext( formatText( data[ 'about' ] ) )
else
textDiv:tag( 'div' ):wikitext( getFixMessage( data[ 'about' ] ) )
node:node( getPseudoBlock( getSeeAlso( seeAlsoCat, isSingle ) ) )
end
else
if data[ 'short' ] then
text = formatText( data[ 'short' ], nil, frame )
end
if text:find( 'class="error"' ) ~= nil then
text = formatText( text, {
type = dtype,
}, frame )
else
text = formatText( text ) .. '.'
end
if data[ 'short' ] == '' then
text = ''
end
local addendum = not isEmpty( seeAlsoCat ) and getSeeAlso( seeAlsoCat, isSingle ) or ''
textDiv:wikitext( text .. addendum )
end
local defaultNs = data[ 'categoryNs' ] or 0
if currentTitle.namespace == defaultNs then
-- Assign default category here since it won’t be otherwise
if isSingle and currentTitle.namespace == 0 then
textDiv:wikitext( setCategory( defaultData[ 'category' ] ) )
end
-- Assign a default sort key if title matches a prefix in config
local defaultSortPrefixes = data[ 'categorySortPrefixes' ]
if isEmpty( defaultSortPrefixes ) ~= nil and type( defaultSortPrefixes ) == 'table' then
local cleanTitle = mw.ustring.gsub( pageNameBase( currentTitle.text ), '[«»„“"\']', '' )
for i, prefix in ipairs( defaultSortPrefixes ) do
local pMatch = mw.ustring.match( cleanTitle, '^' .. escapePattern( prefix ) .. ' ' )
if pMatch ~= nil then
local sortKey = mw.ustring.gsub( cleanTitle, pMatch, '' )
sortKey = mw.text.trim( sortKey )
if sortKey ~= "" then
textDiv:wikitext( frame:preprocess(
string.format( '{{DEFAULTSORT:%s, %s}}', sortKey, pMatch )
) )
break
end
end
end
end
-- Assign the category
textDiv:wikitext( setCategory( data[ 'category' ] ) )
if not isEmpty( data[ 'add' ] ) then
textDiv:wikitext( formatText( data[ 'add' ], nil, frame ) )
end
end
return node
end
-- Chooses which disambiguation type message to show
function p._getPart( frame, dtype, isSingle )
if isEmpty( dtype ) then
return p._renderPart( frame, errorData, isSingle, '*' ), dtype
end
dtype = mw.ustring.lower( dtype )
if dtype:find( '/' ) then
dtype = mw.text.split( dtype, '/' )[ 1 ]
end
local alias = aliases[ dtype ]
if data[ dtype ] == nil and alias == nil then
return p._renderPart( frame, errorData, isSingle, dtype ), dtype
end
if data[ dtype ] ~= nil then
return p._renderPart( frame, data[ dtype ], isSingle, dtype ), dtype
end
return p._renderPart( frame, data[ alias ], isSingle, dtype ), alias
end
-- Documentation for [[Template:Disambig]]
function p._doc( frame )
if frame == nil then
frame = mw.getCurrentFrame()
end
local result = mw.html.create( 'table' )
:addClass( 'wikitable sortable plainlinks' )
result
:tag( 'caption' )
:wikitext( getConfig( 'docTable' ) )
result:tag( 'tr' )
:tag( 'th' )
:attr( 'scope', 'col' )
:wikitext( getConfig( 'docColumnCode' ) )
:tag( 'th' )
:attr( 'scope', 'col' )
:wikitext( getConfig( 'docColumnOutput' ) )
for dtype, val in pairs( data ) do
local als = val[ 'aliases' ]
if dtype:find( '^%-%-' ) == nil or dtype == '--other' then
local displayedType = dtype
local aliases = ''
if dtype == '--other' then
displayedType = als[ 1 ]
end
if als then
for i, alias in ipairs( als ) do
if dtype ~= '--other' or i ~= 1 then
aliases = string.format( '%s, <code>%s</code>', aliases, alias )
end
end
aliases = aliases:gsub( ', ', '', 1 )
aliases = string.format( '<br><i style="opacity:0.85">%s</i>', aliases )
end
-- Replace <li> to <div> here
local template = tostring( p._getPart( frame, dtype, false ) )
template = mw.ustring.gsub( template, '<(/?)li', '<%1div' )
result:tag( 'tr' )
:tag( 'td' )
:attr( 'style', 'font-size:85%' )
:wikitext( string.format( '<code>%s</code>%s', displayedType, aliases ) )
:tag( 'td' )
:attr( 'style', 'font-style:italic' )
:wikitext( template )
end
end
return tostring( result )
end
function p.doc( frame )
return p._doc( frame )
end
-- Generate suggested values in TemplateData blocks
function p._templateData( frame, content )
if isEmpty( content ) then
return ''
end
if frame == nil then
frame = mw.getCurrentFrame()
end
local typeCount = 1
local suggestedValues = {}
for dtype, val in pairs( data ) do
if dtype:find( '^%-%-' ) == nil or dtype == '--other' then
if dtype == '--other' then
suggestedValues[ typeCount ] = val[ 'aliases' ][ 1 ]
else
suggestedValues[ typeCount ] = dtype
end
typeCount = typeCount + 1
end
end
suggestedValues = table.concat( suggestedValues, '", "' )
content = mw.ustring.gsub(
content,
'"suggestedvalues": %[%]',
string.format( '"suggestedvalues": ["%s"]', suggestedValues )
)
return frame:extensionTag{
name = 'templatedata',
content = content,
}
end
function p.templateData( frame )
local args = getArgs( frame )
local content = args[ 1 ]
return p._templateData( frame, content )
end
-- Checks for errors in page code
local function checkErrors( frame, args )
if currentTitle.namespace ~= 0 then
return ''
end
-- Check if current page is a redirect
if currentTitle.isRedirect then
return setCategory( errorData[ 'category' ], '*' )
end
local content = currentTitle:getContent()
if isEmpty( content ) then
return ''
end
content = mw.text.trim( content )
-- Case-insensitive template name
local template = frame:getParent():getTitle()
local mwTitle = mw.title.new( template )
local templatePattern = string.format(
"[%s%s]%s",
mw.ustring.upper( mw.ustring.sub( mwTitle.text, 1, 1 ) ),
mw.ustring.lower( mw.ustring.sub( mwTitle.text, 1, 1 ) ),
mw.ustring.gsub( escapePattern( mwTitle.text ), '^.', '' )
)
-- Check if it is at the start
if mw.ustring.find( content, '{{' .. templatePattern ) == 1 then
return setCategory( errorData[ 'category' ], '↓' )
end
-- Check if it is not the last template
if mw.ustring.find( content, '{{' .. templatePattern .. '[^%}]-}}.-{{' ) ~= nil then
return setCategory( errorData[ 'category' ], '↓' )
end
for key, val in pairs( args ) do
if type( key ) ~= 'number' then
return setCategory( errorData[ 'category' ], '~' )
end
end
return ''
end
-- Protects templates from substitution by substituting them with their own parameters
function p._substing( frame, args, template )
if args == nil then
args = getArgs( frame, {
parentOnly = true,
} )
end
local mTemplateInvocation = require( 'Module:Template invocation' )
local name = mTemplateInvocation.name( template or frame:getParent():getTitle() )
return mTemplateInvocation.invocation( name, args )
end
-- Renders {{Disambig}} template
function p._main( frame, args )
if frame == nil then
frame = mw.getCurrentFrame()
end
local result = mw.html.create( 'div' )
:addClass( 'ts-disambig' )
local reflist = result:tag( 'div' )
:addClass( 'ts-disambig-reflist' )
reflist:tag( 'div' )
:attr( 'role', 'heading' )
:attr( 'aria-level', 2 )
:wikitext( getConfig( 'reflistLabel' ) )
reflist:wikitext( frame:extensionTag{
name = 'references',
} )
local disambig = getWrapper( result )
if isEmpty( args[ 1 ] ) then
local hasTypes = false
for i, dtype in pairs( args ) do
if tonumber( i ) ~= nil and not isEmpty( dtype ) then
hasTypes = true
break
end
end
if not hasTypes then
disambig:node( p._getPart( frame, '--disambig', true ) )
else
disambig:node( p._renderPart( frame, errorData, true, '()' ) )
end
elseif isEmpty( args[ 2 ] ) then
local dtype = args[ 1 ]
disambig:node( p._getPart( frame, dtype, true ) )
else
disambig:node( p._getPart( frame, '--disambig', true ) )
disambig:node( getPseudoBlock( getConfig( 'typesLabel' ), 'ts-disambig-listIntro' ) )
local list = disambig:tag( 'ul' )
:attr( 'role', 'list' )
:addClass( 'ts-disambig-list' )
local hasOther = false
local usedTypes = {}
for i, dtype in ipairs( args ) do
if isNotOther( dtype ) then
local part, usedType = p._getPart( frame, dtype, false )
if not usedTypes[ usedType ] then
list:node( part )
usedTypes[ usedType ] = true
else
list:node( p._renderPart( frame, errorData, false, dtype ) )
end
else
hasOther = true
end
end
if hasOther then
list:node( p._getPart( frame, '--other', false ) )
end
end
getProjectLink( result )
result = tostring( result )
if currentTitle.namespace == 0 then
result = '__DISAMBIG__' .. frame:extensionTag{
name = 'indicator',
content = string.format( '[[File:Disambig.svg|20px|link=%s|%s]]', getConfig( 'helpPage' ), getConfig( 'helpLabel' ) ),
args = { name = '0-disambig' },
} .. result
end
return frame:extensionTag{
name = 'templatestyles',
args = { src = templateStylesPage },
} .. result
end
function p.main( frame )
if mw.isSubsting() then
return p._substing( frame )
end
local args = getArgs( frame )
return p._main( frame, args ) .. checkErrors( frame, args )
end
-- Renders alias templates with substing capabilities
function p.alias( frame )
local args = getArgs( frame )
local template = args[ '$template' ]
args[ '$template' ] = nil
if mw.isSubsting() then
return p._substing( frame, args, 'Template:' .. template )
end
return frame:expandTemplate{
title = template,
args = { args[ 1 ] },
} .. checkErrors( frame, args )
end
-- Renders {{Category disambiguation}} template
function p._category( frame, args )
if frame == nil then
frame = mw.getCurrentFrame()
end
if isEmpty( args[ 2 ] ) and isEmpty( args[ 4 ] ) then
return error( getConfig( 'categoryError' ) )
end
local params = {}
for key, val in pairs( args ) do
if type( key ) == 'number' then
if isEmpty( params[ key - 1 ] ) then
params[ key - 1 ] = params[ key - 1 ] or ''
end
params[ key ] = val
end
end
local result = mw.html.create( 'div' )
:addClass( 'ts-disambig' )
local disambig = getWrapper( result )
disambig:node( p._getPart( frame, '--category', true ) )
disambig:node( getPseudoBlock( getConfig( 'categoryIntro' ), 'ts-disambig-listIntro' ) )
local list = disambig:tag( 'ul' )
:attr( 'role', 'list' )
:addClass( 'ts-disambig-list ts-disambig-categoryList' )
for i, catName in ipairs( params ) do
if i % 2 == 0 and not isEmpty( catName ) then
local catLabel = formatNestedQuotes( catName )
local text = string.format( '[[:Category:%s|%s]]', catName, catLabel )
local label = args[ i - 1 ]
if not isEmpty( label ) then
text = string.format(
'<i>%s</i> — %s ',
label,
formatText( getConfig( 'categoryInLabel' ), {
category = text,
} )
)
else
text = string.format( '<b>%s</b>', text )
end
list:node( getPseudoBlock( text ) )
end
end
getProjectLink( result )
result = tostring( result )
if currentTitle.namespace == 14 then
result = '__DISAMBIG__' .. '__EXPECTUNUSEDCATEGORY__' .. result
end
return frame:extensionTag{
name = 'templatestyles',
args = { src = templateStylesPage },
} .. result
end
function p.category( frame )
if mw.isSubsting() then
return p._substing( frame )
end
return p._category( frame, getArgs( frame ) )
end
return p