Модуль:Disambiguation
Материал из Указатель частей и соединений РККА 1941-1945
Версия от 21:52, 3 марта 2020; Строк (обсуждение | вклад)
Для документации этого модуля может быть создана страница Модуль: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 = '[[Категория:Переадресация:Параметр категория]]' else args['категория'] = nil end -- local ctg = {} -- for u, _ in pairs( args ) do -- if type( u ) ~= 'number' then -- if u == 'категория' then -- table.insert( ctg, '[[Категория:Переадресация:Параметр ' .. 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 tops = mw.text.split( tops, ' *[,] *' ) result.tops = {} result.topsDirectly = {} for _, top in ipairs( tops ) do 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="panel panel-default"><table class="table table-condensed ' .. 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 x = 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.printDateLink( row.page, row.bookpage ) .. '</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 = {} local pageTitleText = mw.title.getCurrentTitle().text 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>' -- ) do local lexer = require( 'Module:Lexer' ) local genp = lexer.declension( pageTitleText, 1 ) or ( '‘' .. pageTitleText .. '’' ) 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 ) 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' ) 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="panel panel-default"> <div class="panel-heading"> <div class="small pull-right in 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="panel-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-condensed"><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>[[Категория:Ошибки переадресации]]' ) 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>[[Категория:Ошибки переадресации в перечнях]]' ) table.insert( out, enumed ) end end return table.concat( out ) end return p