Модуль: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> &nbsp; ' .. 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>' ) )

	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' )
--		.. ' Память народа] &nbsp; &nbsp; &bull; &nbsp; &nbsp; [https://www.yandex.ru/search/?text='
--		.. mw.uri.encode( pageTitleText, 'QUERY' )
--		.. ' Яндекс] &nbsp; &nbsp; &bull; &nbsp; &nbsp; [https://www.google.com/search?ie=UTF-8&hl=ru&q='
--		.. mw.uri.encode( pageTitleText, 'PATH' )
--		.. ' Google] &nbsp; &nbsp; &bull; &nbsp; &nbsp; [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