Модуль:Disambiguation
Материал из Указатель частей и соединений РККА 1941-1945
Для документации этого модуля может быть создана страница Модуль:Disambiguation/doc
--[===[
Соответствующий шаблон располагается на многозначной странице и содержит правила
для всех неоднозначностей. Плюсы:
1. Всегда сразу проверяется исчерпание всех упоминаний
2. Можно проверять даже неоднократное использование
Минусы:
1. Нужно переделать все существующие редиректы и ссылки на них через БС (?)
2. В полный рост встает проблема перекоса (6 иак и 6 иак ПВО; 2 артд и 2 артд (ВП) )
Изначально перекос не является проблемой дизамбига: в таблицах использованы правильные обозначения.
Но косвенно порождаются две проблемы:
* для пользователя неочевидно, что «настоящий» дизамбиг и «ручной» наполнены разным смыслом.
* есть все-таки проблема именования страниц «ручного» дизамбига при перекосах
Возможно, правильно считать, что ручного дизамбига вообще не бывает, и все должно покрываться похожими названиями
3. Сборная страница реального юнита в общем случае порождает гораздо более сложный where.
Параметры.
получается, что шаблон должен иметь сериальные параметры, по каждому варианту нужно задать
имя страницы и включить/исключить. Поэтому синтаксис должен базироваться на позиционных параметрах.
1 = юнит $ включить $ исключить
--]===]
local p = {}
--local global = mw.ext.luaglobal
local cargo = mw.ext.cargo
local ref = require( 'Module:Ref' )
local report = require( 'Module:Report' )
local tools = require( 'Module:Tools' )
local cat = require( 'Module:Categories' ) -- loadData не позволяет пользоваться #... и т. п.
local enumeration = require( 'Module:Enumeration' )
function p.Disambiguation( frame )
-- local args = tools.checkargs( frame:getParent().args, {
-- ['категория'] = true,
-- }, true ) -- разрешены позиционные параметры
-- По-новому: =(пусто) или просто позиционный параметр — это редирект, ограничений нет
-- =- — это ручной дизамбиг, никакие разделения титульного юнита не требуются
-- правила через точку с запятой, «период/подчинение», после $ — набор исключений
-- Терминология: каждый юнит — дескриптор, каждый набор правил clauses, отдельное правило clause
local args = {}
for u, v in pairs( frame:getParent().args ) do
if type( u ) == 'number' then
args[mw.text.trim( v )] = ''
else
args[u] = v
end
end
local ctg = args['категория'] or ''
if ctg ~= '' then
ctg = '[[Category:Переадресация:Параметр категория]]'
else
args['категория'] = nil
end
-- local ctg = {}
-- for u, _ in pairs( args ) do
-- if type( u ) ~= 'number' then
-- if u == 'категория' then
-- table.insert( ctg, '[[Category:Переадресация:Параметр ' .. u .. ']]' )
-- else
-- end
-- end
return ctg .. p._disambig( args, frame )
end
local function todate( str, last )
if str == '' then
return ''
end
local p = mw.text.split( str, '.', true )
assert( #p <= 3 )
local d, m, y
y = tonumber( p[#p] )
if #p ~= 1 then
m = tonumber( p[#p-1] )
end
if #p == 3 then
d = tonumber( p[1] )
end
if y < 1900 then
y = y + 1900
end
if not m then
if last then
m = 12
else
m = 1
end
end
if not d then
if last then
if m == 2 and y % 4 == 0 then -- вековых годов нет
d = 29
else
d = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
d = d[m]
end
else
d = 1
end
end
return y .. '-' .. string.sub( '0' .. m , -2, -1 ) .. '-' .. string.sub( '0' .. d , -2, -1 )
end
local function parseClauses( arg )
if arg == '' then
return nil
end
local clauses = mw.text.split( arg, '%s*;%s*' )
local resultA = {}
-- local simple = true
-- local collect = {}
for _, clause in ipairs( clauses ) do
if clause ~= '' then
local result = {}
local tops = string.find( clause, '/', 1, true )
local dates = ''
if not tops then
dates = clause
tops = ''
else
dates = mw.text.trim( string.sub( clause, 1, tops-1 ) )
tops = mw.text.trim( string.sub( clause, tops+1, -1 ) )
end
if dates ~= '' then
local a, b = string.match( dates, '^([%d%.]*) *%- *([%d%.]*)$' )
assert( a )
result.from = todate( a )
result.to = todate( b, true )
else
result.from = ''
result.to = ''
end
if tops ~= '' then
local hiddenBrackets = {}
tops = string.gsub( tops, '%b()', function ( a )
table.insert( hiddenBrackets, a )
return '/' .. string.char( 47+#hiddenBrackets ) -- чтобы следующий симвод точно был не слэш и не запятая
end )
tops = mw.text.split( tops, ' *[,] *' )
result.tops = {}
result.topsDirectly = {}
for _, top in ipairs( tops ) do
top = string.gsub( top, '/(.)', function ( a )
return hiddenBrackets[string.byte( a ) - 47]
end )
if string.sub( top, 1, 1 ) == '=' then
table.insert( result.topsDirectly, ( report.bigfuller( mw.text.trim( string.sub( top, 2, -1 ) ) ) ) )
else
table.insert( result.tops, ( report.bigfuller( top ) ) )
end
end
if #result.tops == 0 then
result.tops = nil
end
if #result.topsDirectly == 0 then
result.topsDirectly = nil
end
end
table.insert( resultA, result )
end
end
return resultA
end
local function packClauses( clauses )
if not clauses then
return ''
end
local str = {}
for _, clause in ipairs( clauses ) do
table.insert( str, clause.from .. ';' .. clause.to .. ';'
.. table.concat( clause.tops or {}, ',' ) .. ';'
.. table.concat( clause.topsDirectly or {}, ',' ) )
end
return table.concat( str, '$' )
end
local function checkClause( row, dsc )
if dsc.from ~= '' and row.date < dsc.from or dsc.to ~= '' and row.date > dsc.to then
return nil
end
if not dsc.topsDirectly and not dsc.tops then
return true
end
if dsc.topsDirectly then
for _, u in ipairs( dsc.topsDirectly ) do
if row.id1 == u or row.s_id0 == u then
return true
end
end
end
if dsc.tops then
for _, u in ipairs( dsc.tops ) do
if row.id1 == u or row.id2 == u or row.id3 == u or row.id4 == u or row.id5 == u or row.id6 == u or row.s_id0 == u then
return true
end
end
end
return nil
end
local function checkIE( row, clauses )
if not clauses then
return 0
end
for i, clause in ipairs( clauses ) do
if checkClause( row, clause ) then
return i
end
end
return nil
end
local function printTable( clauses, class )
local out = {}
table.insert( out, '<div class="card panel-default"><table class="card-body table table-sm ' .. class .. '"><tr class=small><th>Период</th><th>Подчинение</th><th>Исп.</th></tr>' )
for _, clause in ipairs( clauses ) do
table.insert( out, '<tr><td>' )
if clause.from ~= '' or clause.to ~= '' then
table.insert( out, report.printDate( clause.from ) .. '-' .. report.printDate( clause.to ) )
else
table.insert( out, '<i>без ограничений</i>' )
end
table.insert( out, '</td><td>' )
if clause.tops or clause.topsDirectly then
local x = {}
if clause.tops then
x = { '[[' .. table.concat( clause.tops, ']]<br>[[' ) .. ']]' }
end
if clause.topsDirectly then
table.insert( x, '[[' .. table.concat( clause.topsDirectly, ']] <i>(непосредственное подчинение)</i><br>[[' ) .. ']] <i>(непосредственное подчинение)</i>' )
end
table.insert( out, table.concat( x, '<br>' ) )
else
table.insert( out, '<i>любое подчинение</i>' )
end
table.insert( out, '</td><td>' .. ( clause.used or '—' ) .. '</td></tr>\n' )
end
table.insert( out, '</table></div>' )
return table.concat( out )
end
local function printRow( row )
local str = {}
for i = 0, 6 do
local x = report.outEl( row, i, 2, i ~= 0 )
if x then
table.insert( str, x )
else
break
end
end
return '<b>' .. report.printDateWithLink( row ) .. '</b> ' .. table.concat( str, ' / ' )
end
local function deRoma( u )
return ( string.gsub( u, '[IV]+', { I = 1, II = 2, III = 3, IV = 4, V = 5, VI = 6 } ) )
end
--function compareUnitLt( u1, u2 )
-- local n1, n2 = string.match( u1, '^(%d+) ' ), string.match( u2, '^(%d+) ' )
-- if tonumber( n1 or 0 ) ~= tonumber( n2 or 0 ) then -- не-число меньше числа
-- return tonumber( n1 or 0 ) < tonumber( n2 or 0 )
-- end
-- -- числа равны или оба без чисел
-- if n1 then
-- u1 = string.match( u1, '^%d* ?(.+$)' )
-- u2 = string.match( u2, '^%d* ?(.+$)' )
-- end
function p._disambig( args, frame )
local out = {}
ref.refStart()
local pageTitleText = mw.title.getCurrentTitle().text
table.insert( out, frame:preprocess( '<indicator name=link-rkka><span class="d-none admin-view">[{{fullurl:rkka:' .. pageTitleText .. '|action=edit&redirect=no}} <i class="fa fa-link"></i>]</span></indicator>' ) )
do -- 2025-08: Категории по номерам/названиям
local firstWord = string.match( pageTitleText, '^([%d]+)' ) or string.match( pageTitleText, '^([^ ]+)' )
local bigCat, sort
local numb = tonumber( firstWord )
if numb then
if numb < 500 then
bigCat = 'с номером ' .. numb
elseif numb < 1200 then
bigCat = 'с номерами ' .. math.floor( numb / 10 ) .. 'x'
elseif numb < 2000 then
bigCat = 'с номерами ' .. math.floor( numb / 100 ) .. 'xx'
elseif numb < 3000 then
bigCat = 'с номерами 2xxx'
else
bigCat = 'с номерами 3000 и далее'
end
sort = string.sub( '-------' .. firstWord, -7, -1 )
else
bigCat = 'с наименованиями без номера'
sort = pageTitleText
end
table.insert( out, '[[Category:Формирования ' .. bigCat .. '|' .. sort .. ']]' )
end
local baseName, tmp = string.match( pageTitleText, '^([^(]+) %((.+)%)$' )
if baseName then
table.insert( out, frame:callParserFunction{ name = 'DISPLAYTITLE', args = baseName .. ' <small>(' .. tmp .. ')</small>' } )
else
baseName = pageTitleText
end
-- table.insert( out, '<div><div class=rkka-editor><div class="panel panel-default"><div class=panel-body><div class=center><b>[https://pamyat-naroda.ru/warunit/?q='
-- .. mw.uri.encode( baseName, 'PATH' )
-- .. ' Память народа] • [https://www.yandex.ru/search/?text='
-- .. mw.uri.encode( pageTitleText, 'QUERY' )
-- .. ' Яндекс] • [https://www.google.com/search?ie=UTF-8&hl=ru&q='
-- .. mw.uri.encode( pageTitleText, 'PATH' )
-- .. ' Google] • [https://www.bing.com/search?q='
-- .. mw.uri.encode( pageTitleText, 'PATH' )
-- .. ' Bing]</b></div></div></div></div></div>'
-- )
local lexer = require( 'Module:Lexer' )
local genp = lexer.declension( pageTitleText, 1 ) or ( '‘' .. pageTitleText .. '’' )
if ( frame.args.more or '' ) ~= '' then
table.insert( out, '<p class="lead error">Для просмотра состава ' .. genp .. ' по состоянию на ' .. tools.dmy( frame.args.more )
.. ' необходимо выбрать основное наименование этого формирования.</p>' )
else
table.insert( out, frame:callParserFunction{ name = '#seo', args = { '',
description = 'Боевой состав и подчинение ' .. genp .. '.',
keywords = 'боевой состав Советской Армии,Великая Отечественная война,' .. pageTitleText,
}
} )
end
local ctgtable
local ctg = args['категория']
local nom, gen
if not ctg then
local tmp = string.match( pageTitleText, '^(.-) %(' ) -- считаем, что иностранцев не бывает (либо у них явно задана категория)
ctg = report.simplecat( tmp or pageTitleText )
ctg = mw.ustring.lower( mw.ustring.sub( ctg, 1, 1 ) ) .. mw.ustring.sub( ctg, 2, -1 )
end
if not cat.names[ctg] then
error( 'Необходимо указать существующую категорию формирования вместо «' .. ctg .. '».' )
else
ctgtable = cat.names[ctg]
nom = ctgtable.group
gen = cat.groups[ctgtable.group].gen or 'формирования'
end
local num = string.match( pageTitleText, '^(%d+)' )
table.insert( out, '[[Category:' .. ctg .. '|' .. string.sub( '0000' .. ( num or '' ), -4, -1 ) .. pageTitleText .. ']]' )
args['категория'] = nil
local descriptors = {}
local list = {}
local sorter = {}
for unit, val in pairs( args ) do
if val ~= '-' then
local splitVal = mw.text.split( val, '%s*%$%s*' )
splitVal[2] = splitVal[2] or ''
if #splitVal ~= 2 then
error( 'Ошибка. Описатель перенаправления ' .. unit
.. ' не соответствует формату «наименование формирования = критерии применения [$ критерии исключения]».' )
end
local inc, exc = parseClauses( splitVal[1] ), parseClauses( splitVal[2] )
if not inc and not exc then
inc = { { from = '', to = '' } }
end
table.insert( descriptors, {
unit = unit,
inc = inc,
exc = exc,
} )
table.insert( sorter, descriptors[#descriptors] )
else
table.insert( list, { unit = unit, tableList = true } )
table.insert( sorter, list[#list] )
end
end
table.sort( sorter, function ( a, b ) return deRoma( a.unit ) < deRoma( b.unit ) end )
local special
if #descriptors == 1 and not next( list ) then
table.insert( out, '<p>Это страница относится к альтернативному наименованию формирования (в «[[Project:Научно-справочный труд «Боевой состав Советской Армии»|Боевом составе Советской Армии]]» для одного и того же формирования использованы различные обозначения). Полная информация о формировании приводится на странице <b>[[' .. descriptors[1].unit .. ']]</b>.</p>' )
special = 'a'
else
-- table.insert( out, '<p>Это страница разрешения неоднозначностей (в «[[Project:Научно-справочный труд «Боевой состав Советской Армии»|Боевом составе Советской Армии]]» одно и то же обозначение относится к нескольким формированиям).</p>\n' .. frame:preprocess('__DISAMBIG__') .. '\n' )
table.insert( out, '<p class=lead>На этой странице перечислены те формирования, к которым может относиться наименование «' .. pageTitleText .. '».</p>\n'
.. frame:preprocess('__DISAMBIG__') .. '\n' )
special = 'm'
end
local query = cargo.query(
'units=t0, units=t1, units=t2, units=t3, units=t4, units=t5, units=t6, '
.. 'units=t0s, units=t1s, units=t2s, units=t3s, units=t4s, units=t5s, units=t6s',
't0.fix=date, t0.war=war, t0.fixunit=porno, '
.. 't0.unit=id0, t0.addendum=add0, t0.ref=ref0, t0.creating=form0, t0.ass=ass0, t0.tab=tab0, t0.hq=hq0, t0s.unit=s_id0, t0s.addendum=s_add0, t0s.ref=s_ref0, '
.. 't1.unit=id1, t1.addendum=add1, t1.ref=ref1, t1.creating=form1, t1.ass=ass1, t1.tab=tab1, t1.hq=hq1, t1s.unit=s_id1, t1s.addendum=s_add1, t1s.ref=s_ref1, '
.. 't2.unit=id2, t2.addendum=add2, t2.ref=ref2, t2.creating=form2, t2.ass=ass2, t2.tab=tab2, t2.hq=hq2, t2s.unit=s_id2, t2s.addendum=s_add2, t2s.ref=s_ref2, '
.. 't3.unit=id3, t3.addendum=add3, t3.ref=ref3, t3.creating=form3, t3.ass=ass3, t3.tab=tab3, t3.hq=hq3, t3s.unit=s_id3, t3s.addendum=s_add3, t3s.ref=s_ref3, '
.. 't4.unit=id4, t4.addendum=add4, t4.ref=ref4, t4.creating=form4, t4.ass=ass4, t4.tab=tab4, t4.hq=hq4, t4s.unit=s_id4, t4s.addendum=s_add4, t4s.ref=s_ref4, '
.. 't5.unit=id5, t5.addendum=add5, t5.ref=ref5, t5.creating=form5, t5.ass=ass5, t5.tab=tab5, t5.hq=hq5, t5s.unit=s_id5, t5s.addendum=s_add5, t5s.ref=s_ref5, '
.. 't6.unit=id6, t6.addendum=add6, t6.ref=ref6, t6.creating=form6, t6.ass=ass6, t6.tab=tab6, t6.hq=hq6, t6s.unit=s_id6, t6s.addendum=s_add6, t6s.ref=s_ref6, '
.. 't0._pageName=page, t0.page=bookpage',
{
where = 't0.unit="' .. pageTitleText .. '"',
join = 't0.fixparent=t1.fixunit, t0.fixstruct=t0s.fixunit, '
.. 't1.fixparent=t2.fixunit, t1.fixstruct=t1s.fixunit, '
.. 't2.fixparent=t3.fixunit, t2.fixstruct=t2s.fixunit, '
.. 't3.fixparent=t4.fixunit, t3.fixstruct=t3s.fixunit, '
.. 't4.fixparent=t5.fixunit, t4.fixstruct=t4s.fixunit, '
.. 't5.fixparent=t6.fixunit, t5.fixstruct=t5s.fixunit, '
.. 't6.fixstruct=t6s.fixunit ',
orderBy = 'date, porno', -- по порно, чтобы дубликаты в одинаковой последовательности шли
} )
if #query == 0 and next( descriptors ) then
table.insert( out, '<p>В представленной на сайте версии справочника «Боевой состав Советской Армии» все упоминания ' .. gen .. ' снабжены соответствующими уточнениями. Ниже для каждого варианта указываются критерии, по которым выбиралось соответствующее формирование.</p>' )
else
table.insert( out, [=[
<div class="card panel-default">
<div class="card-header">
<div class="small pull-right show collapse all-tab" data-toggle="collapse" data-target=".all-tab" >[<i>[[#0|показать]]</i>]</div>
<div class="small pull-right collapse all-tab" data-toggle="collapse" data-target=".all-tab">[<i>[[#0|скрыть]]</i>]</div>
<b>Все упоминания данного наименования ]=] .. gen .. [=[ в «[[Project:Научно-справочный труд «Боевой состав Советской Армии»|Боевом составе Советской Армии]]»</b></div>
<div class="card-body collapse all-tab">
]=] )
table.insert( out, ( require('Module:Page').parents( 't0.unit="' .. pageTitleText .. '"' ) ) )
table.insert( out, '</div></div>' )
end
for _, row in ipairs( query ) do
for _, dsc in ipairs( descriptors ) do
local inc = checkIE( row, dsc.inc )
if inc then
if inc ~= 0 then
dsc.inc[inc].used = ( dsc.inc[inc].used or 0 ) + 1
end
local exc = checkIE( row, dsc.exc )
if exc then
if exc ~= 0 then
dsc.exc[exc].used = ( dsc.exc[exc].used or 0 ) - 1
else
row.used = ( row.used or 0 ) + 1
end
else
row.used = ( row.used or 0 ) + 1
end
end
end
end
for _, dsc in ipairs( sorter ) do
table.insert( out, '<h3>[[' .. dsc.unit .. ']]</h3>' )
if dsc.tableList then
table.insert( out, '<p>Упоминания этого формирования уточнены непосредственно в таблицах боевого состава и отображаются на [['
.. dsc.unit .. '|странице формирования]] без дополнительных корректировок.</p>' )
else
if dsc.inc then
table.insert( out, '<p><i class="fa fa-plus-circle" style=color:#0e900e></i> К этому формированию относятся следующие упоминания ' .. gen .. ':</p>' )
table.insert( out, printTable( dsc.inc, 'amb-inc' ) )
end
if dsc.exc then
if dsc.inc then
table.insert( out, '<p><i class="fa fa-times-circle" style=color:#900e0e></i> Часть указанных упоминаний ' .. gen .. ' относится к <b>другим</b> формированиям. <b>Исключаются</b> упоминания, соответствующие следующим критериям:</p>' )
else
table.insert( out, '<p><i class="fa fa-times-circle" style=color:#900e0e></i> К данному формированию относятся все упоминания ' .. gen .. ', <b>за исключением</b> следующих:</p>' )
end
table.insert( out, printTable( dsc.exc, 'amb-exc' ) )
end
end
end
local errorFlag
for _, row in ipairs( query ) do
if row.used ~= 1 then
if not errorFlag then
table.insert( out, '<h3>Отсутствующие и дублирующиеся перенаправления</h3>' )
table.insert( out, '<table class="table table-sm"><tr class=small style=background-color:#f8f8f8><th>Упоминание</th><th>Исп.</th></tr>' )
errorFlag = true
end
table.insert( out, '<tr><td>' .. printRow( row ) .. '</td><td>' .. ( row.used or '-' ) .. '</td></tr>' )
end
end
if errorFlag then
table.insert( out, '</table>[[Category:Ошибки переадресации]]' )
end
for _, dsc in ipairs( descriptors ) do
table.insert( out, frame:expandTemplate{ title = 'Таблица переадресации', args = {
redirectname = mw.ustring.upper( mw.ustring.sub( dsc.unit, 1, 1 ) ) .. mw.ustring.sub( dsc.unit, 2, -1 ),
selector = packClauses( dsc.inc ) .. '#' .. packClauses( dsc.exc ),
} } )
end
table.insert( out, frame:expandTemplate{ title = 'Таблица страниц', args = {
unit = pageTitleText,
class = ctg,
number = string.match( pageTitleText, '^%d+' ),
guard = string.match( pageTitleText, 'гв%. ' ) ~= nil,
standalone = string.match( pageTitleText, 'отд%. ' ) ~= nil,
first = nil,
last = nil,
special = special,
} } )
if #descriptors ~= 1 or next( list ) then
local enumed = enumeration.unitQuery( pageTitleText )
if enumed then
table.insert( out, '<h3>Неоднозначные ссылки в Перечнях</h3>[[Category:Ошибки переадресации в перечнях]]' )
table.insert( out, enumed )
end
end
table.insert( out, ref.refOut() )
return table.concat( out )
end
return p