Модуль:Вложенный список (Bk;rl,&Flk'yuudw vhnvkt)
Перейти к навигации
Перейти к поиску
Этот модуль оценён как готовый к использованию. Предполагается, что все баги устранены и он готов для широкого использования. Его можно указывать на справочных страницах и рекомендовать к использованию новым участникам. Для его изменения и тестирования, пожалуйста, используйте песочницу. |
Данный модуль реализует шаблон {{Вложенный список}}. После перехода на модуль стало возможно заменить старый код шаблона на более доступный и семантичный и начать отслеживать ошибки в коде выводимых страниц.
Код вызова модуля: {{#invoke:Вложенный список|main}}
.
Категории
{{Вложенный список}} добавляет ряд штрафных категорий. Для упрощения их исправления модулем добавляются отображаемые при предпросмотре сообщения со ссылками на страницы, из-за которых шаблоном добавляется штрафная категория.
- Википедия:Страницы с ошибками шаблона Вложенный список (136) — ставится в случаях:
- несуществования страницы или отсутствия текста при попытке вставить вложенный список (например, в случае некорректной разметки в нём).
- наличия технических ошибок «Медиавики» в выводе вложенного списка.
- наличия в выводе шаблонов:
- {{неоднозначность}}
- {{ФИО}} и аналогичных
- {{К переименованию}} и аналогичных.
- {{TOC right}} и аналогичных.
- {{примечания}}
- ошибок в разметке списков с точки зрения вики-кода и доступности:
- начала вложенного списка не с разметки элемента списка
*
. - вложенных списков с неправильной вложенностью (через
:*
или с**
или с;
в начале).
- начала вложенного списка не с разметки элемента списка
- вложенных списков с заголовками 1-го и 2-го уровня (
==
) в выводе. - вложенных списков с вертикальной чертой (
----
) в выводе. - вложенных списков с ссылками в тексте (все ссылки должны оформлены через
<ref>
). - вложенных списков, в которых вложенные списки не входят в подсписки (
{{NL|Пример}}
вместо* {{NL|Пример}}
).
- Википедия:Страницы с шаблоном Вложенный список с неверным типом страницы значений (1524) ставится в случаях, когда использование вложенного списка противоречит руководству Википедия:Неоднозначность#Вложенные списки (страница не входит в категории тёзок и однофамильцев). В некоторых случаях подобное исправляется через объединение подстраницы вложенного списка с основной страницей, в других через замену на простую ссылку или исправление типов на подстранице вложенного списка.
- Википедия:Страницы с шаблоном Вложенный список без ссылок (224) ставится в случаях, когда в коде вложенного списка нет ссылок на другие страницы. В некоторых случаях такая категория может ставиться, если в начале пункта списка до ссылки есть какой-то произвольный текст. В таких случаях этот текст нужно разместить после ссылки или после тире.
require( 'strict' )
--
-- Implements {{Вложенный список}} in one module
-- Allows us to potentially move towards a more accessible markup for the template when the time is right
-- Previously, the template broke the lists {{Вложенный список}} or {{NL2}} or {{NL3}} are in
-- Heavily borrows from https://en.wikipedia.org/wiki/Module:Excerpt_slideshow
--
local getArgs = require( 'Module:Arguments' ).getArgs
local templatePageName = 'Вложенный список'
local modulePageName = 'Вложенный список'
local disambigPageSuffix = ' (значения)'
local editLinkText = 'править'
-- [[:Категория:Википедия:Страницы с ошибками шаблона Вложенный список]]
local errorCat = 'Википедия:Страницы с ошибками шаблона Вложенный список'
-- [[:Категория:Википедия:Страницы с шаблоном Вложенный список без ссылок]]
-- Remove the line to stop tracking this category
local linklessCat = 'Википедия:Страницы с шаблоном Вложенный список без ссылок'
-- [[:Категория:Википедия:Страницы с шаблоном Вложенный список с неверным типом страницы значений]]
-- Remove the line to stop tracking this category
local wrongTypeCat = 'Википедия:Страницы с шаблоном Вложенный список с неверным типом страницы значений'
local goodTypeCategories = {
[ 'Страницы значений:Тёзки' ] = true,
[ 'Страницы значений:Однофамильцы' ] = true,
[ 'Страницы значений:Однофамильцы-тёзки' ] = true,
[ 'Страницы значений:Полные тёзки' ] = true,
}
local errorMsgs = {
noTitle = 'Нет названия страницы.',
invalidTitle = 'Неправильное название страницы <code>%s</code>.',
noContent = 'Ошибка при включении страницы «[[%s]]».',
hasErrors = 'Ошибка в коде вложенного списка «[[%s]]» ([[Шаблон:Вложенный список#Штрафные категории|см. документацию]]).',
noLinks = 'На странице «[[%s]]» нет ссылок на другие страницы.',
notDisambig = 'На странице «[[%s]]» находится статья.',
wrongType = 'Страница «[[%s]]» не является страницей тёзок или однофамильцев. См. [[ВП:Н/ВС]].',
}
-- Adds a replacement to a redirected variant when substed
local substWithRedirects = true
local moduleInvocationCode = '{{#invoke:' .. modulePageName .. '|main|'
local nestedCheckArg = '$$no_checks'
local currentTitle = mw.title.getCurrentTitle()
local p = {}
local function isEmpty( str )
return str == nil or str == ''
end
local function addWarning( title, message )
if title ~= nil and message == nil then
message = string.format( noContent, title )
end
if title ~= nil and message ~= nil then
message = string.format( message, title )
end
mw.addWarning( string.format( '[[Template:%s|%s]]: %s', templatePageName, templatePageName, message ) )
end
local function templatePattern( str )
return string.format(
"[%s%s]%s",
mw.ustring.upper( mw.ustring.sub( str, 1, 1 ) ),
mw.ustring.lower( mw.ustring.sub( str, 1, 1 ) ),
mw.ustring.gsub( str, '^.', '' )
)
end
local function noGoodCategories( mwTitle )
if mwTitle == nil then
return true
end
local success, data = pcall( function()
return mwTitle.categories
end )
if not success then
return false
end
for _, category in ipairs( data ) do
if goodTypeCategories[ category ] == true then
return false
end
end
return true
end
local function findTwice( str, pattern )
if isEmpty( str ) then
return 0
end
local first, firstPos = mw.ustring.find( str, pattern )
if first == nil then
return 0
end
return mw.ustring.find( str, pattern, firstPos ) ~= nil and 2 or 1
end
local function checkForErrors( sublist )
-- Templates/syntax that should not be there
if
mw.ustring.find( sublist, 'class="error' ) ~= nil
or mw.ustring.find( sublist, 'id="disambig"' ) ~= nil -- {{disambig}}
or mw.ustring.find( sublist, 'class="hatnote' ) ~= nil -- {{hatnote}}
or mw.ustring.find( sublist, ' ambox ambox-' ) ~= nil -- {{ambox}}
or mw.ustring.find( sublist, '__TOC__' ) ~= nil -- __TOC__
or mw.ustring.find( sublist, 'UNIQ--references' ) ~= nil -- {{references}}
then
return true, '❌'
end
-- Incorrect list markup
if
mw.ustring.find( sublist, '^%s*%*' ) ~= 1 -- does not start with a list
or mw.ustring.find( sublist, '^%s*%*%*' ) ~= nil
or mw.ustring.find( sublist, '\n%:%*' ) ~= nil
then
return true, '*'
end
-- Other incorrect markup
if
mw.ustring.find( sublist, '\n;%s*[А-ЯA-Z]' ) ~= nil -- incorrect bold text markup
or mw.ustring.find( sublist, '\n=[^=]' ) ~= nil -- 1st level heading
or mw.ustring.find( sublist, '\n==[^=]' ) ~= nil -- 2nd level heading
or mw.ustring.find( sublist, '\n%-%-%-%-' ) ~= nil -- contains ---- in code
or mw.ustring.find( sublist, '\n<div class="ts%-NL">' ) ~= nil -- {{NL}} after a line break
then
return true, '🙈'
end
-- links inline
if mw.ustring.find( sublist, '[%[%s]https?:%/%/' ) ~= nil then
return true, '🔗'
end
return false, ' '
end
local function getEditLink( mwTitle )
local span = mw.html.create( 'span' )
:addClass( 'ts-NL-edit mw-editsection-like plainlinks noprint navigation-not-searchable group-user-show' )
:wikitext( string.format(
'<span class="mw-editsection-bracket">[</span>[%s %s]<span class="mw-editsection-bracket">]</span>',
mwTitle:fullUrl( 'action=edit' ),
editLinkText
) )
return tostring( span )
end
local function getIntro( title, text, addendum, mwTitle )
if isEmpty( title ) then
return error( 'getIntro: no title' )
end
text = text and mw.text.trim( text ) or ''
addendum = addendum or ''
-- Remove bracketed suffix automatically from a disambig page name
if isEmpty( text ) and mw.ustring.find( title, disambigPageSuffix, 1, true ) ~= nil then
text = mw.ustring.gsub( title, '^%s*(.+)%s+%b()%s*$', '%1' )
end
if not isEmpty( text ) then
text = '|' .. text
end
local colon = ':'
if not isEmpty( addendum ) then
colon = ''
addendum = string.format( ' %s:', addendum )
end
local intro = string.format(
'<span class="dabhide">[[%s%s]]%s</span>%s',
title,
text,
colon,
addendum
)
return intro .. getEditLink( mwTitle )
end
local function getError( title, msg, setCategory )
if setCategory == nil then
setCategory = true
end
local errorText = msg
if title ~= nil and title ~= '' then
errorText = string.format( msg, title )
end
addWarning( nil, errorText )
return '<div class="error">' .. errorText .. '</div>'
.. ( setCategory and string.format( '[[Category:%s]]', errorCat ) or '' )
end
-- Prevent template loop by replacing template calls to the calls to this module
local function replaceSubtemplates( content )
if isEmpty( content ) then
return content
end
local invocation = moduleInvocationCode .. nestedCheckArg .. '=1|'
content = mw.ustring.gsub( content, '{{' .. templatePattern( templatePageName ) .. '/?[23]?|', '{{NL|' )
content = mw.ustring.gsub( content, '{{' .. templatePattern( 'NL' ) .. '/?[23]?%|', invocation )
return content
end
--[[
@param {String} wikitext: Wikitext of just the list (i.e. each line is a list item)
@param {String} symbol: Special character used in the wikitext markup for the list, e.g. '*' or '#'
@param {String} outerTag: Text portion of the tag for each list or sublist, e.g. 'ul' or 'ol'
@param {String} innerTag: Text portion of the tag for each list item, e.g. 'li'
]]
local wikitextToHtmlList = function( wikitext, symbol, outerTag, innerTag )
local listParts = {}
for level, item in mw.ustring.gmatch( '\n' .. wikitext .. '\n', '\n(%' .. symbol .. '+)(.-)%f[\n]' ) do
table.insert( listParts, { level=level, item=item } )
end
table.insert( listParts, { level='', item='' } )
local htmlList = {}
for i, this in ipairs( listParts ) do
local isFirstItem = ( i == 1 )
local isLastItem = ( i == #listParts )
local lastLevel = isFirstItem and '' or listParts[ i - 1 ][ 'level' ]
local tags
if #lastLevel == #this.level then
tags = '</'..innerTag..'><'..innerTag..'>'
elseif #this.level > #lastLevel then
tags = string.rep( '<'..outerTag..'><'..innerTag..'>', #this.level - #lastLevel )
elseif isLastItem then
tags = string.rep( '</'..innerTag..'></'..outerTag..'>', #lastLevel )
else -- ( #this.level < #lastLevel ) and not last item
tags = string.rep( '</'..innerTag..'></'..outerTag..'>', #lastLevel - #this.level ) .. '</'..innerTag..'><'..innerTag..'>'
end
table.insert( htmlList, tags .. this.item )
end
return table.concat( htmlList )
end
--[[
@param {String} wikitext: Wikitext excerpt containg zero or more lists
@param {String} symbol: Special character used in the wikitext markup for the list, e.g. '*' or '#'
@param {String} outerTag: Text portion of the tag for each list or sublist, e.g. 'ul' or 'ol'
@param {String} innerTag: Text portion of the tag for each list item, e.g. 'li'
]]
local gsubWikitextLists = function( wikitext, symbol, outerTag, innerTag )
-- temporarily remove list linebreaks...
wikitext = mw.ustring.gsub( wikitext .. '\n', '\n%' .. symbol, '¿¿¿' .. symbol )
-- ...so we can grab the whole list (and just the list)...
return mw.ustring.gsub(
wikitext,
'¿¿¿%'..symbol..'[^\n]+',
function( listWikitext )
-- ...and then reinstate linebreaks...
listWikitext = mw.ustring.gsub( listWikitext, '¿¿¿%' .. symbol, '\n' .. symbol )
-- ...and finally do the conversion
return wikitextToHtmlList( listWikitext, symbol, outerTag, innerTag )
end
)
end
-- Protects the templates from substitution by substituting them with their own parameters
function p._substing( frame )
local args = getArgs( frame, {
parentOnly = true,
} )
local mTemplateInvocation = require( 'Module:Template invocation' )
local name = mTemplateInvocation.name( frame:getParent():getTitle() )
if substWithRedirects then
name = mw.ustring.gsub( name, 'Вложенный список/?%d?', 'NL' )
name = mw.ustring.gsub( name, 'NL/?%d?', 'NL' )
end
return mTemplateInvocation.invocation( name, args )
end
function p.main( frame )
if mw.isSubsting() then
return p._substing( frame )
end
local args = getArgs( frame )
local title = args[ 1 ]
local linkText = args[ 2 ]
local appendedText = args[ 3 ]
if isEmpty( title ) then
return getError( nil, errorMsgs.noTitle, currentTitle.namespace == 0 )
end
-- frame:expandTemplate is used because mw.title:getContent() does not handle redirects
local mwTitle = mw.title.new( title )
-- Invalid title
if mwTitle == nil then
return getError( title, errorMsgs.invalidTitle )
end
local origMwTitle = mwTitle
if mwTitle.redirectTarget then
mwTitle = mwTitle.redirectTarget
end
-- The page or redirect target is equal to current page
if mw.title.equals( mwTitle, currentTitle ) then
return getIntro( title, linkText, appendedText, origMwTitle )
.. getError( title, errorMsgs.noContent )
end
local content = mwTitle:getContent()
content = replaceSubtemplates( content )
local sublist = frame:preprocess( content )
-- The page returns empty list
if isEmpty( content ) or isEmpty( sublist ) or mw.text.trim( sublist ) == '' then
return getIntro( title, linkText, appendedText, origMwTitle )
.. getError( title, errorMsgs.noContent )
end
-- Reject transclusions that look like articles
if mw.ustring.find( sublist, ' class="infobox' ) ~= nil
or mw.ustring.find( sublist, ' class="navbox' ) ~= nil then
return getIntro( title, linkText, appendedText, origMwTitle )
.. getError( title, errorMsgs.notDisambig )
end
-- Remove 3rd+ level subheadings for easier section insertion (before error checks)
sublist = mw.ustring.gsub( sublist, '\n(===+)(.-)%1\n-', '' )
-- Check sublist for wikitext markup errors
local hasErrors, errorType = checkForErrors( sublist )
-- Replace list markers with HTML list openers
sublist = gsubWikitextLists( '\n' .. sublist, '*', 'ul', 'li' )
sublist = gsubWikitextLists( '\n' .. sublist, '#', 'ol', 'li' )
-- Remove the bold text around links automatically
sublist = mw.ustring.gsub( sublist, "<li>%s*'''%s*%[%[([^%]]+)%]%]%s*'''", '<li>[[%1]]' )
-- Trim and replace double line breaks to avoid breaking the list
sublist = mw.text.trim( sublist )
sublist = mw.ustring.gsub( sublist, '\n\n', '<p>' )
-- Merge adjacent lists
sublist = mw.ustring.gsub( sublist, '</ul>\n-<ul>', '' )
-- Replace remaining
sublist = mw.ustring.gsub( sublist, '\n', '<br>' )
-- Disable nested checks
local notNestedCheck = isEmpty( args[ nestedCheckArg ] )
-- Check if the included page has more than two links
if linklessCat then
-- Two plain wikitext links
local linkCount = findTwice( mw.text.killMarkers( content ), '%*%s*\'*["«„]?\'*%[%[' )
if linkCount < 2 then
-- Two module invocations mean two different links
linkCount = linkCount + findTwice( content, moduleInvocationCode )
end
if linkCount < 2 then
-- [[Модуль:Не переведено]]
linkCount = linkCount
+ findTwice( sublist, ' версия статьи «' )
+ findTwice( sublist, ' title="Элемент статьи «' )
end
if linkCount < 2 then
-- Only add a category on the page itself
if notNestedCheck then
sublist = sublist .. string.format( '[[Category:%s]]', linklessCat )
end
addWarning( title, errorMsgs.noLinks )
end
end
-- Check if the included page has wrong type
if wrongTypeCat and notNestedCheck then
if noGoodCategories( mwTitle ) then
sublist = sublist .. string.format( '[[Category:%s]]', wrongTypeCat )
addWarning( title, errorMsgs.wrongType )
end
end
if hasErrors then
sublist = sublist .. string.format( '[[Category:%s|%s]]', errorCat, errorType )
addWarning( title, errorMsgs.hasErrors )
end
local intro = getIntro( title, linkText, appendedText, mwTitle )
return '<div class="ts-NL">' .. intro .. sublist .. '</div>'
end
return p