Модуль:Unit

Материал из Указатель частей и соединений РККА 1941-1945
Перейти к:навигация, поиск

Для документации этого модуля может быть создана страница Модуль:Unit/doc

local p = {}
local global = mw.ext.luaglobal
local tools = require( 'Module:Tools' )
local ref = require( 'Module:Ref' )
local cargo = mw.ext.cargo

local REF_ORIG = 'o'
local REF_MY = 'm'

-- !, :{Формирование указано в таблице на текущую дату дважды. Это может быть связано с процессом переподчинения.},
-- !, :{В оригинале указан * (он же указан в составе *). В составе 3 мк был 1 мцп, см. [[вп:Механизированный корпус РККА]].},
-- !, :Неверная аббревиатура в оригинале,
-- , !, :Неверная аббревиатура в оригинале
-- | * /corp/В оригинале отсутствуют указания на точку окончания перечисления соединений и частей, входящих в состав корпуса; при подготовке материала для размещения на сайте могла быть допущена ошибка.

--[[ ИЗ СОСТАВА
Формы
1) 310 сп (140 сд) ==  ф | 310 | сп | /( | 140 сд | ) | == на основе слова «/(» понимаем, что следующий элемент надо не в стек запихивать, а делать запись в особой табличке «обособленные дети»
2) 140 сд (без 310 сп) == это просто комментарий
3) 1/415 гап == заносим обычным образом  в основную таблицу с таким номером (но ссылка должна быть на 415 гап)
4) 190 гап (один дивизион из)
5) 190 гап (без одного дивизиона)
6) 4 акп: ...

--]]

--[[
pageunits
- это название — разнописание основного
- это название — общее название для нескольких формирований
- это название индивидуализирует формирование
- это набор преобразований одного формирования (вопрос: при нынешней схеме мы не можем в базе по имени найти разнописания)





Осталось сделать:
2) разобраться с управлениями/полевыми управлениями
9) разноформатные табы в разных разделах
12) все доп.аргументы (сноски, текст, и проч. для ПОДЧИНЕНИЯ) — невозможно сделать нормально, ему не соответствует юнит
13) проверить аргумент текст для АРМИЯ
18) ENG
19) убрать что-нибудь в php
20) разобраться с недействующей частью ЗакФ
21) группировки: Требуется группировать
	(а) тезкт. в зависимости от существования главного тезки, страница с перечнем тезок именуется либо |«ХХХХ (*)»|, либо |«ХХХХ»|
	(б) поколения/преобразования/переименования. Эта страница является парной к полному имени (включающему скобки).
		Поэтому тут логично использовать не скобки, а подстраницу. |«YYYY/преобразования»|. Или так |«YYYY — преобразования»|.
	И то, и другое ищем по базе, а не через _pageexists_
23) 2 ПрибФ 12.1944 — много бригад из состава 6 гв. адп!!!
24) Дооформить «восстановленный БС» — примечания и проч.
25) Перейти на единые процедуры для примечаний
26) решить, что делать с правками (пометки у зачеркиваний или примечания)
27) переносить в базу флаг «управление»


+ 1) из состава
+ 3) примечания (сделать и расставить)
+ 4) наглядные исправления опечаток
+ 5) иностранные формирования «~/»
+ 5а) групповая добавка ВП для польских армий
+ 5б) страна РККА внутри иностранной группы (для нашего соединения, подчиненного иностранцам)
+ 6) хвост для различения омонимов
+ 7) ~ххх при списке номеров (брмп балтфлота 01.1942,  внутренняя оборона Ленинграла 01-07.1943, иностранцы)
+ 8) шаблон:Раздел ПВО
+ 10) Превращать «гв. отд.» в «отд. гв.». Делать после стыковки с номером (на случай, если гв. — часть номера)!
+ 11) Dry для {{армия}} и проч.
+ 14) собственная система примечаний (автоматическое определение дублей)
на каждую группу комментариев: счетчик (он же признак, что комментарии прочитаны) URefCount(O/M), сам массив комментариев URef(O/M), флаг записи, Номер части URefPart (o/m)
структура массива: [текст комментария]={номер, число обратных ссылок}
+ 15) группировка иностранных формирований в разделе «Состав»
+ 16) похожие наименования
+ 17) «~(...)» — эквивалент «/(...)» плюс добавление в название
+ 22) сверхбольшие составы (2 УкрФ, ЗапФ)

--]]

local data = mw.loadData( 'Module:Data' )
local formation -- = mw.loadData( 'Module:Formation' ) переставил загрузку к месту использования

local uStack, modStack, uPorno --, uDry
local lastU, bsDate, uTab, uDBmode, uDBcount, uPage
local inFormation, pageId, lastForeign, DB -- живут в пределах одного шаблона


--- local uRefPart, uRefCount, uRef, modRef = {}, {}, {}, {}
modRef = modRef or {}


local function fStart()
	uStack = global.get( 'UStack' ) -- сохраняется по modStack
	lastU = global.get( 'LastU' ) or '' -- сохраняется всегда
	uDBcount = global.get( 'UDBcount' ) or 0
	uPorno = global.get( 'UPorno' ) or 0 -- сохраняется всегда (и всегда соответствует lastU, если оно установлено!
	uTab = global.get( 'UTab' ) -- сохраняется вручную
	uPage = global.get( 'UPage' ) -- сохраняется вручную
	DB = {}
end

local function flashDB()
	local res = ''
	if #DB then
		local frame = mw.getCurrentFrame()
		for _, u in ipairs( DB ) do
			if uDBmode == 2 then
				global.add( 'RKKA-log', u )
			else
				res = res .. frame:expandTemplate{ title = 'Таблица формирований', args = u }
			end
		end
		DB = {}
	end
	return res
end


local function fReturn( text )
	if modStack then
		global.set( 'UStack', uStack )
		modStack = nil
	end

	global.set( 'LastU', lastU )
	global.set( 'UPorno', uPorno )
	global.set( 'UDBcount', uDBcount )
--- 	return text
	text = text .. flashDB()
	if next( modRef ) then
		return ref.refReturn( text )
	else
		return text
	end
end

local function closeTabs()
	-- завершаем текущий таб. Отсутствующие табы дозаполнять не надо
	if uTab then
		uTab = nil
		global.set( 'UTab', nil )
		return p._closeTab() .. '</div>' -- tab, tab-wrap и row-wrap
	end
	return ''
end

function p.Part( frame )
	local args = tools.checkargs( frame:getParent().args, {
			'',
		} )
	local x = tonumber( args[1] )
	assert ( x and x >= 0 and x <= 9, 'Недопустимый номер части таблицы боевого состава' )
	global.set( 'UPartBS', x )
	local tit = mw.title.getCurrentTitle().fullText .. '/' .. x
-- <div class="rkka-editor pull-right">[{{fullurl:{{FULLPAGENAME}}/{{{1|}}}|action=edit}} <i class="glyphicon glyphicon-pencil"></i>]</div>{{ {{FULLPAGENAME}}/{{{1|x}}} }}
	return '<div class="rkka-editor pull-right">['
		.. tostring( mw.uri.fullUrl( tit, { action = 'edit' } ) )
		.. '<i class="glyphicon glyphicon-pencil"></i>]</div>'
		.. frame:expandTemplate{ title = tit }
end



function p.Write( frame )
	local args = tools.checkargs( frame:getParent().args, {
			'',
		} )
	uDBmode = global.get( 'UDBmode' )
	local mytitle = mw.title.getCurrentTitle()
	local stamp, part = string.match( mytitle.text, ' (%d%d%.%d%d%.%d%d%d%d)/(.)$' )
	local locked = ( mw.title.new( 'РККА:Блокировка БД' ).id or 0 ) ~= 0
--[[
заменяем uDry на uDBmode: nil — никакой записи, 1 — блокировка страницей-семафором, 2 — протокол,
у нас есть типы страниц с шаблоном «Запись»: «пишущие части», «тестовые», «публичные»
и страница-семафор

Шаблон Запись (начало):
- /1 + семафор — nil
- /1 без семафора —  1
- /t - 2
- основная — 3

на основных мы считаем в UDBcount число записей в базу
--]]

	if args[1] == 'конец' then
		if uDBmode == 1 then
			return 	frame:expandTemplate{ title = 'Таблица формирований', args = {
								unit = 'Запись в БД', tab = 'финиш',
							} }
		elseif uDBmode == 2 then
--			return mw.dumpObject( global.get( 'RKKA-log' ) )
			local log, res = global.get( 'RKKA-log' ), {}
			for _, t in ipairs( log ) do
				local row = {}
				table.insert( row, t.unitId or '—' )
				table.insert( row, '[[' .. ( t.unit or '—' ) .. ']]' )
				table.insert( row, '<i>' .. ( t.parentId or '—' ) .. '</i>' )
				table.insert( row, '<i>[[' .. ( t.parent or '—' ) .. ']]</i>' )
				table.insert( row, t.structParentId or '—' )
				table.insert( row, t.addendum or '—' )
				table.insert( row, t.ref or '—' )
				table.insert( row, t.ass or '—' )
				table.insert( row, t.tab or '—' )
				table.insert( row, t.creating or '—' )
				table.insert( res, '<tr><td>' .. table.concat( row, '</td><td>' ) .. '</td></tr>\n' )
			end
			return '<table class="table table-condensed table-hover>'
				.. '<tr><th>Id</th><th>Unit</th><th>pId</th><th>Parent</th><th>strP</th><th>add</th><th>ref</th><th>Ass</th><th>Tab</th><th>Create</th></tr>\n'
				.. table.concat( res ) .. '</table>'
		elseif uDBmode == 3 then
--			do return '(' .. ( global.get( 'UPartBS' ) or ' ((?)) ' ) .. ')<br>' end
--			local tmp = '/' .. global.get( 'UPartBS' )
--			tmp = mw.title.getCurrentTitle() .. '/'
			local dbCount = cargo.query( 'units', 'COUNT(_pageID)=X',
				{ where = '_pageTitle="' .. mw.title.getCurrentTitle().text .. '/' .. global.get( 'UPartBS' ) .. '"' } )
			local luaCount = global.get( 'UDBcount' )
			frame:expandTemplate{ title = 'Таблица контроля БД', args = {
								pagel = luaCount, basel = dbCount[1].X - 2,
							} }
			if  luaCount ~= dbCount[1].X - 2 then
				return '<p class="rkka-editor error">Ошибка! В базе ' .. ( dbCount[1].X - 2 ) .. ' записей вместо ' .. luaCount .. '</p>'
			else
--				return '<p class="rkka-editor">В базе ' .. dbCount[1].X .. ' записей</p>'
			end
			global.set( 'UDBcount', 1 ) --09022019
		else
			return
		end
	end
	local txt = ''
	if tonumber( part ) then
		if locked then
			uDBmode = nil
			txt = '<p class=error>Запись в базу данных [[РККА:Блокировка БД|<span class=error>заблокирована!</span>]]</p>'
		else
			uDBmode = 1
			frame:expandTemplate{ title = 'Таблица формирований', args = {
								unit = 'Запись в БД', tab = 'старт',
							} }
		end
		txt = txt .. '[[Категория:Боевой состав|'
			.. string.sub( stamp, 7, 10 ) .. string.sub( stamp, 4, 5 ) .. string.sub( stamp, 1, 2 )
			.. part .. ']]'
	elseif part then
		uDBmode = 2
	else
		uDBmode = 3
		global.set( 'UDBcount', 0 ) --09022019
	end
	global.set( 'UDBmode', uDBmode )
	return txt
end




local function writeDB( frame, args )
	if uDBmode == 1 then
--		return frame:expandTemplate{ title = 'Таблица формирований', args = args }
		table.insert( DB, args )
	elseif uDBmode == 2 then
--		global.add( 'RKKA-log', args )
		table.insert( DB, args )
	elseif uDBmode == 3 then
		uDBcount = uDBcount + 1
	end
	return ''
end


function p.BS( frame )
	local args = tools.checkargs( frame:getParent().args, {
			'', '', ['текст'] = ''
		} )
	return p._bs( args, frame )
end
p['Боевой состав'] = p.BS

function p._bs( args, frame )
	local bsDateArg = args[1]
	if bsDateArg == 'конец'
			or bsDateArg == 'разрыв' and mw.title.getCurrentTitle().subpageText ~= mw.title.getCurrentTitle().text then
		fStart()
		ref.refStart()
		local x = closeTabs() -- .. '<br>'
--		x = x .. '<hr style="margin:0;border:1px solid #aaa">' -- плохо из-за первого раздела ПВО
		global.set( 'BSDate', nil )
		x = x .. ref.refOut()
		return fReturn( x )
	end
	if bsDateArg == '' then
		local x = mw.title.getCurrentTitle().text
		bsDateArg = string.match( x, '(194%d%-%d%d%-%d%d)' )
		if not bsDateArg then
			bsDateArg = string.match( x, '(%d%d%.%d%d%.194%d)' )
			if bsDateArg then
				bsDateArg = string.sub( bsDateArg, 7, 10 ) .. '-' .. string.sub( bsDateArg, 4, 5 ) .. '-' .. string.sub( bsDateArg, 1, 2 )
			end
		end
	end
	if ( bsDateArg or '' ) == '' then
		error( '<p class=error>Непонятна дата</p>' )
	end
	bsDate = global.get( 'BSDate' )
	if bsDate then  -- это разрыв и в конце прежнего файла, и в начале нового
--		error( '<p class=error>Не закрыт предыдущий раздел!</p>' )
		if bsDateArg ~= 'разрыв' then
			global.set( 'UDBcount', 1 )
		end
		return '<i></i>'
	end
	bsDate, lastU = bsDateArg, args[2]
	local title = args[2]
	local descr = data.Tabs[lastU]
	if not descr then
		error( '<p class=error>Несуществующий раздел</p>' )
	end
	lastU = descr.unit or title


	global.set( 'BSDate', bsDate )
	global.set( 'UStack', { { war = descr.war, unit = title, ass = 1, porno = descr.code } } )
	local db = ''
	if descr.code ~= '' then
		DB = DB or {}
		uDBmode = global.get( 'UDBmode' )
		uDBcount = global.get( 'UDBcount' ) or 0
		db = writeDB( frame, {
				fix = bsDate,
				unit = lastU,
--				parent = nil,
				unitId = mw.title.getCurrentTitle().id .. ':' .. descr.code,
--				parentId = nil,
--				structParentId = structParent,
--				creating = inFormation,
				war = descr.war,
--				ass = assLvl,
--				ref = refer,
--				addendum = addendum,
--				partial = nil,
			} )
		flashDB()
		global.set( 'UDBcount', uDBcount )
	end
	local level = descr.level or 2
	return '<h' .. level .. '>' .. title
--		.. ' <small>по состоянию на ' .. string.sub( bsDate, 9, 10 ) .. '.'
--		.. string.sub( bsDate, 6, 7 ) .. '.' .. string.sub( bsDate, 1, 4 )
--		.. '</small>'
		.. '</h' .. level .. '>' .. db
end



function p.Open( frame )
	local args = tools.checkargs( frame:getParent().args, {
			''
		} )
	fStart()
	return fReturn( p._openLevel( args[1] ) )
end
p['('] = p.Open

function p._openLevel( arg, assLvl )
	table.insert( uStack, { unit = lastU, war = uStack[#uStack].war, ass = assLvl, porno = uPorno, gene = lastForeign or uStack[#uStack].gene } )
	modStack = true
	lastU = nil
	local out = '('
	if arg == '-' then
		out = ''
	end
	return out
end


function p.Close( frame )
	local args = tools.checkargs( frame:getParent().args, {
			''
		} )
	fStart()
	return fReturn( p._closeLevel( args[1] ) )
end
p[')'] = p.Close

function p._closeLevel( arg )
	table.remove( uStack )
	modStack = true
	lastU = nil
	if arg == '-' then
		return ''
	end
	return ')'
end

function p.F( frame )
	local args = tools.checkargs( frame:getParent().args, {
			['текст'] = true, f = true,
		}, true )
-- TODO  Проверка на ошибки

	fStart()
	bsDate = global.get( 'BSDate' )
--	uDry = global.get( 'UNoDry' )
--	if uDry == true then
--		uDry = nil
--	elseif uDry ~= 'protocol' then
--		uDry = ''
--	end
	uDBmode = global.get( 'UDBmode' )
--	uDBcount = global.get( 'UDBcount' )

	pageId = mw.title.getCurrentTitle().id


	local res = {}
	local empty = global.get( 'UTabEmpty' )
	if empty then
		res = { '<p>' }
		global.set( 'UTabEmpty', nil )
	end

	local modeRevers
	local n1, n2
	local corrForReversText, corrForReversDB
	local function pop( revStruct )
		if n2 then
			table.insert( res, p._f( frame, args, n1, n2, nil, revStruct, modeRevers == 3 ) )
			n2 = nil
		end
	end

	for i, x in ipairs( args ) do
		if x == '(' then
			pop()
			table.insert( res, ' ' .. p._openLevel( '' ) )
		elseif x == ')' then
			if modeRevers == 2 then
				modeRevers = nil
				pop( 2 )
				table.insert( res, ']' )
			elseif modeRevers == 3 then
				modeRevers = nil
				pop( 2 )
--				global.add( 'DEBUGLOG', corrForReversDB .. ': ' .. type( DB[corrForReversDB] ) .. ', ' .. type( DB[corrForReversDB].unit ) )
--				global.add( 'DEBUGLOG', #DB .. ': ' .. type( DB[#DB] ) .. ', ' .. type( DB[#DB].unit ) )
				for iDB = corrForReversDB+1, #DB-1 do
					DB[iDB].unit = string.gsub( DB[iDB].unit, '%(%)',  '(' .. DB[#DB].unit .. ')' )
				end
				res[corrForReversText+1] =  string.gsub( res[corrForReversText+1], '%(%)',  '(' .. DB[#DB].unit .. ')' )
				table.insert( res, ']' )
			else
				pop()
				if uStack[#uStack].ass then
					table.insert( res, '<br><span class=error>ОШИБКА! Лишняя закрывающая скобка записи о корпусе[[Категория:Ошибки скобок]]</span><br>' )
				else
					table.insert( res, p._closeLevel( '' ) )
				end
			end
		elseif x == ',' or x == ';' or x == '.' then
			if modeRevers then
				table.insert( res, '<br><span class=error>ОШИБКА! Ссылка на вышестоящее формирование не завершилась скобкой[[Категория:Ошибки скобок]]</span><br>' )
			end
			pop()
			table.insert( res, x .. ' ' )
		elseif x == '/('  then
			modeRevers = 2  -- обычный modeRevers
			pop( 1 )
			table.insert( res, ' [<i></i>' )
		elseif x == '~('  then
			modeRevers = 3  -- modeRevers с добавкой в названия юнитов
			corrForReversDB = #DB
			corrForReversText = #res
			pop( 1 )
			table.insert( res, ' [<i></i>' )
		elseif x == '\\('  then
			table.insert( res, ' [<i></i>' )
			pop( 2 )
			table.insert( res, '] (' )
			p._openLevel( '-' )
			uStack[#uStack].struct = true
		elseif string.sub( x, 1, 1 ) == '/' then
			pop()
			if not empty then
				table.insert( res, '<br> ' )
			end
			formation = formation or mw.loadData( 'Module:Formation' )
			inFormation = formation.FUPcode[string.sub( x, 2, -1 )]
			if inFormation ~= '' then
				table.insert( res, '<i>На ' .. formation.FUP[inFormation] .. ': </i>' )
			end
		elseif n2 then
			n2 = i
		else
			n1 = i
			n2 = i
		end
		empty = false
	end
	pop()
--	global.set( 'LastU', lastU ) --  fReturn сохранит
	return fReturn( table.concat( res ) )
end
p['Ф'] = p.F


local function gvotd( str )
	return string.gsub( str, 'гв%. отд%.', 'отд. гв.', 1 )
end

local function id( num )
	if tonumber( num ) then
		return pageId .. ':' .. string.sub( '00000' .. num, -5, -1 )
	end
	return pageId .. ':' .. num
end

function p._f( frame, args, start, stop, assInfo, revStruct, addStruct )
	local assLvl, assTab, assPage
	if assInfo then
		assLvl, assTab = assInfo[1], assInfo[2]
	end
	assPage = uPage

	-- assLvl нужен для восстановления структуры на страницах формирований при извлечении из базы
	-- revStruct
	----- revStruct = 1 — полк в формате «полк (дивизия)». В этом случае в качестве structParent мы занесем porno+1
	----- revStruct = 2 — признак собственно structParent. В этом случае в качестве Parent мы занесем «СТРУКТУРА». И это всегда уникальное/единичное обозначение
	----- addStruct = true — для полка в формате «полк (дивизия)» — к полку добавить «()», чтобы потом заменить на «(дивизия)»
	local err = {} -- массив сообщений об ошибках
	local parent, structParent = uStack[#uStack]
	if parent.struct then
		parent, structParent = uStack[#uStack-1], parent
		if revStruct == 1 then
			table.insert( 'Слуга двух господ' )
		end
	end

	if addStruct then
		addStruct = ' ()'
	else
		addStruct = ''
	end

	local numbers = args[start]
	local abbr = args[start+1]  -- это то, что мы будем расшифровывать
	local prabbr = abbr -- так должно выглядеть на печати по умолчанию
	local refer, addendum, onlyPU, post, hidden, foreign -- *xxx, +xxx, --, ++, ~xxx  /////create,
	if args['для'] then
		refer = { '-' .. args['для'] }
	elseif uStack[#uStack].forfor and not uStack[#uStack].forTop then
		refer = { '-' .. uStack[#uStack].forfor }
	else
		refer = {}
	end
	local arg
	for i = stop, start+1, -1 do
		arg = args[i]
		if arg == '--' then
			onlyPU = true
		else
			local t = string.sub( arg, 1, 1 )
			if t == '*' then
				table.insert( refer, mw.text.trim( string.sub( arg, 2, -1 ) ) )
			elseif t == '+' then
				addendum = mw.text.trim( string.sub( arg, 2, -1 ) )
			elseif t == '~' then
				local u = string.sub( arg, 1, 2 )
				if u == '~+' then
					post = ' ' .. mw.text.trim( string.sub( arg, 3, -1 ) )
				elseif string.sub( arg, 1, 2 ) == '~/' then
					u = data.Foreign[mw.text.trim( string.sub( arg, 3, -1 ) )]
					if u then
						foreign = ' (' .. u .. ')'
					else
						table.insert( err, 'Неизвестное обозначение иностранного формирования' )
					end
				else
					hidden = ' (' .. mw.text.trim( string.sub( arg, 2, -1 ) ) .. ')'
				end
			else
				arg = i
				break
			end
		end
		arg = start
	end
	if #refer == 0 then
		refer = nil
	else
		refer = table.concat( refer, '$' )
	end

	lastForeign = foreign or uStack[#uStack].gene
--	foreign = lastForeign

	stop = arg or stop

-- мы не знаем количество элементов списка, для уникального формирования он может быть и пустым, и непустым (если там есть модификатор).
	if start == stop then
		abbr = numbers -- это то, что мы будем расшифровывать
		prabbr = numbers -- так должно выглядеть на печати по умолчанию
		numbers = {} -- номерной части нет
	else
		if stop ~= start+1 then
			table.insert( err, 'Лишние сегменты в описании формирования' )
			stop = start+1
		end
		local hiddencomments = {}
		numbers = string.gsub( numbers, '%{([^}]+)%}', function ( a )
				table.insert( hiddencomments, a )
				return '{' .. #hiddencomments .. '}'
			end )
		numbers = mw.text.split( numbers, '[%s]*,[%s]*' )
		for i, u in ipairs( numbers ) do
			numbers[i] = string.gsub( u, '%{(%d+)%}', function ( a )
					return hiddencomments[tonumber( a )]
				end )
		end
		if #numbers ~= 1 and numbers[#numbers] == '' then -- удаляем последнюю запятую, надо про это не забыть: каким-то маркерам это может быть важно
			table.remove( numbers )
		end
	end

	local descr = data.Units[abbr]
	local otd = ''
	local changed
	repeat
		changed = false
		if type( descr ) == 'string' then
			abbr = descr
			descr = data.Units[abbr]
			changed = true
		end
		if not descr then
			if mw.ustring.sub( abbr, 1, 5 ) == 'отд. ' then
				otd = 'отд. '
				abbr = mw.ustring.sub( abbr, 6, -1 )
				descr = data.Units[abbr]
				changed = true
			end
			if not descr and mw.ustring.sub( abbr, 1, 4 ) == 'гв. ' then
				otd = otd .. 'гв. '
				abbr = mw.ustring.sub( abbr, 5, -1 )
				descr = data.Units[abbr]
				changed = true
			end
		end
		if descr and descr.alt then
			if descr.alt[uTab] then
				abbr = descr.alt[uTab]
				descr = data.Units[abbr]
				changed = true
			elseif not descr.alt[1] then
				table.insert( err, 'Неоднозначное обозначение формирования «' .. abbr .. '» в колонке «' .. uTab .. '»' ) -- (' .. log .. ')
				descr = { class = 'unknown' }
				changed = false
			end
		end
	until not changed

	local tabf = {}
	local modif, marking, striking, refInList, oncehidden
	for _, numb in ipairs( numbers ) do
		if modif then
			-- маркеры
			---- !, зачеркнутое:комментарий, -- включение зачеркнутого текста с возможным комментарием
			---- !, !, -- выделение ссылки
			---- !, :примечание, — только для списков. На будущее надо предусмотреть «*{ примечание с запятыми }»
			---- !, ~добавка (аналог ~+)
			if numb == '!' then
				marking = true
			else
				if not hidden and string.sub( numb, 1, 1 ) == '~' then
					oncehidden = ' (' .. mw.text.trim( string.sub( numb, 2, -1 ) ) .. ')'
				else
					local colon = string.find( numb, ':', 1, true )
					if colon == 1 then
						refInList = string.sub( numb, 2, -1 )
					else
						if colon then
							numb, colon = string.sub( numb, 1, colon-1 ), string.sub( numb, colon+1, -1 )
						else
							colon = '(текст исключен)'
						end
						striking = '<s><span class=striked title="' .. colon .. '">&nbsp;' .. numb .. '&nbsp;</span></s> '
					end
				end
			end
			modif = nil
		elseif numb == '!' then
			modif = true
		else
			local refDB
			if refInList and refer then
				refDB = refInList .. '$' .. refer
			else
				refDB = refInList or refer
			end
			table.insert( tabf, { striked = striking, marked = marking, text = numb, refPrint = refInList, refDB = refDB, ownhidden = oncehidden } )
			marking, striking, refInList, oncehidden = nil, nil, nil, nil
		end
	end
	if modif then
		table.insert( err, 'Список закончился модификатором' )
	end
	if not descr then
		table.insert( err, 'Неизвестное обозначение формирования «' .. abbr .. '»' )
		descr = { class = 'unknown' }
	elseif descr.uniq and #tabf ~= 0 then
		table.insert( err, 'Указан номер уникального формирования' )
	end

	local res
	if #tabf == 0 then
		if not descr.uniq then
			table.insert( err, 'Отсутствует номер регулярного формирования' )
		end

		local text
		if args.f then
			text = descr[args.f]
		end
		text = args['текст'] or ( otd .. ( text or prabbr ) .. ( post or '' ) .. ( foreign or '' ) )
		if marking then
			text = '<u>' .. text .. '</u>'
		end

		lastU = otd .. ( descr.n or abbr ) .. ( post or '' ) .. ( hidden or '' ) .. addStruct .. ( lastForeign or '' )

		res = ( striking or '' ) .. '[[' .. lastU
		if text ~= lastU then
			res = res .. '|' .. text
		end
		uPorno = uPorno + 1
		if revStruct == 1 then
			structParent = id( uPorno + 1 )
		elseif structParent then
			structParent = id( structParent.porno )
		end
		if revStruct == 2 then
			res = res .. ']]'
				.. writeDB( frame, {
						fix = bsDate,
						unit = lastU,
						parent = parent.unit,
						unitId = id( uPorno ),
						parentId = 'структура',
						structParentId = structParent,
						creating = inFormation,
						war = parent.war,
						ass = assLvl,
						addendum = addendum,
						ref = refer,
					} )
		else
			res = res .. ']]'
				.. writeDB( frame, {
						fix = bsDate,
						unit = lastU,
						parent = parent.unit,
						unitId = id( uPorno ),
						parentId = id( parent.porno ),
						structParentId = structParent,
						creating = inFormation,
						war = parent.war,
						ass = assLvl,
						asstab = assTab,
						page = assPage,
						ref = refer,
						addendum = addendum,
						tab = uTab,
						hq = onlyPU,
					} )
		end
	elseif #tabf == 1 then
		if tabf[1].striked then
			striking = tabf[1].striked .. ( striking or '' )
		end
		if tabf[1].marked then
			marking = true
		end
		refer = tabf[1].refDB
		local text
		if args.f and descr[args.f] then
			text = otd .. descr[args.f]
		end
		text = ( text or prabbr ) .. ( post or '' )

		local numbertext = tabf[1].text
		local slash
		if numbertext == '-' then
			if not post then
				text = text .. ' <span style="letter-spacing:-0.3ex; font-style:italic;">&nbsp;<sup>б</sup>/<sub>№</sub></span>&nbsp;'
			end
			numbertext = ''
		else
			slash = string.find( numbertext, '/', 1, true )
			if slash then
				slash, numbertext = string.sub( numbertext, 1, slash ), string.sub( numbertext, slash+1, -1 )
				text = '<span class=nolink>' .. slash .. '</span>' .. numbertext .. ' ' .. text
			else
				text = numbertext .. ' ' .. text
			end
			numbertext = numbertext .. ' '
		end
		text = text .. ( foreign or '' )

		text = args['текст'] or text

		if marking then
			text = '<u>' .. text .. '</u>'
		end

		lastU = gvotd( numbertext .. otd .. ( descr.n or abbr ) .. ( post or '' ) .. ( hidden or tabf[1].ownhidden or '' ) .. addStruct  .. ( lastForeign or '' ) )

		res = ( striking or '' ) .. '[[' .. lastU
		if text ~= lastU then
			res = res .. '|' .. text
		end
		uPorno = uPorno + 1
		if revStruct == 1 or slash then
			structParent = id( uPorno + 1 )
		elseif structParent then
			structParent = id( structParent.porno )
		end
		if slash then
			uPorno = uPorno + 1
			res = res .. ']]'
				.. writeDB( frame, {
						fix = bsDate,
						unit = slash,
						parent = parent.unit,
						unitId = id( uPorno - 1 ),
						parentId = id( parent.porno ),
						structParentId = structParent,
						creating = inFormation,
						war = parent.war,
--						ass = 9,   -- 11.01.2019 кажется, никому это не нужно
						ref = tabf[1].refDB,
						addendum = addendum,
						tab = uTab,
						hq = onlyPU,
					} )
				.. writeDB( frame, {
						fix = bsDate,
						unit = lastU,
						parent = parent.unit,
						unitId = id( uPorno ),
						parentId = 'структура',
--						structParentId = structParent,
--						creating = inFormation,
--						war = parent.war,
--						ass = assLvl,
--						ref = refer,
--						partial = nil,
					} )
		elseif revStruct == 2 then
			res = res .. ']]'
				.. writeDB( frame, {
						fix = bsDate,
						unit = lastU,
						parent = parent.unit,
						unitId = id( uPorno ),
						parentId = 'структура',
						structParentId = structParent,
						creating = inFormation,
						war = parent.war,
						ass = assLvl,
						asstab = assTab,
						page = assPage,
						addendum = addendum,
						ref = tabf[1].refDB,
--						partial = nil,
					} )
		else
			res = res .. ']]'
				.. writeDB( frame, {
						fix = bsDate,
						unit = lastU,
						parent = parent.unit,
						unitId = id( uPorno ),
						parentId = id( parent.porno ),
						structParentId = structParent,
						creating = inFormation,
						war = parent.war,
						ass = assLvl,
						asstab = assTab,
						page = assPage,
						addendum = addendum,
						ref = tabf[1].refDB,
						tab = uTab,
						hq = onlyPU,
					} )
		end
	else
		local commonU = ' ' .. otd .. ( descr.n or abbr ) .. ( post or '' ) .. ( hidden or '' ) .. addStruct  .. ( lastForeign or '' )
		if revStruct == 1 then
			structParent = id( uPorno + #tabf + 1 )
		elseif structParent then
			structParent = id( structParent.porno )
		end
		res = {}
		for _, f in ipairs( tabf ) do
			local text = f.text
			if f.marked then
				text = '<u>' .. text .. '</u>'
			end
			lastU = gvotd( f.text .. commonU .. ( f.ownhidden or '' ) )
			local ownref = f.refPrint
			if ownref then
				ownref = ref.use( ownref )
			else
				ownref = ''
			end
			uPorno = uPorno + 1
			table.insert( res, ( f.striked or '' ) .. '[[' .. lastU .. '|' .. text .. ']]'
				.. ownref
				.. writeDB( frame, {
						fix = bsDate,
						unit = lastU,
						parent = parent.unit,
						unitId = id( uPorno ),
						parentId = id( parent.porno ),
						structParentId = structParent,
						creating = inFormation,
						addendum = addendum,
						war = parent.war,
						ass = assLvl,
						asstab = assTab,
						page = assPage,
						ref = f.refDB,
						tab = uTab,
						hq = onlyPU,
					} ) )
		end

		local text = args['текст']
		if text then
			if text == '-' then
				text = ''
			else
				text = ' ' .. text
			end
			if striking or marking then
				table.insert( 'Модификатор предопределенного текста' )
			end
		else
			if args.f and descr[args.f] then
				text = otd .. descr[args.f]
			end
			text = ( text or prabbr ) .. ( post or '' ) .. ( foreign or '' )
			if marking then
				text = '<u>' .. text .. '</u>'
			end
			text = ' ' .. ( striking or '' ) .. text
		end
		res = table.concat( res, ', ' ) .. text
	end
	if refer then
		res = res .. ref.use( refer )
	end
	if addendum then
		res = res .. ' <i>(' .. addendum .. ')</i>'
	end

	if onlyPU then
		res = res .. ' <i>(управление)</i>'
	end

	if #err ~= 0 then
		err = '<br><span class=error>ОШИБКА! ' .. table.concat( err, '<br>' ) .. '</span>[[Категория:Ошибки в указании формирования]]<br>'
	else
		err = ''
	end

	return res .. err
end


local function closeAss( lvl )
	-- закрыть все этажи, включая lvl
	local res = {}
	-- мы должны были предварительно сходить на closeTabs
	if not uStack[#uStack].ass then
		error( 'Не закрыты Табы!!! ' )
	end
	local n = 0
	for i = #uStack, 1, -1 do
		if uStack[i].ass < lvl then
			n = i
			break
		end
	end
	for _ = 1, #uStack-n do
		p._closeLevel( '-' )
	end
	return table.concat( res )
end

local function buildChain()
	local chain = ''
	for i = #uStack, 2, -1 do
		chain = chain .. ' / [[' .. uStack[i].unit .. ']]'
	end
	return chain
end

function p.Assoc( frame )
	local args = tools.checkargs( frame:getParent().args, {
			'',
			['таб'] = true, ['для'] = true,
		}, true )
-- TODO  Проверка на ошибки
	fStart()
	bsDate = global.get( 'BSDate' )
--	uDry = global.get( 'UNoDry' )
--	if uDry == true then
--		uDry = nil
--	elseif uDry ~= 'protocol' then
--		uDry = ''
--	end
	uDBmode = global.get( 'UDBmode' )
	pageId = mw.title.getCurrentTitle().id

	local ass = mw.text.trim( mw.ustring.lower( frame.args[1] ) )
	local assLvl = data.Assocs[ass]
	if not assLvl then
		error( 'Неизвестный уровень группировки ' .. ass )
	end

	-- завершение ранее открытых tab
	local tab = closeTabs()

	-- завершение открытых уровней, более мелких или равных нашему
	local resAss = closeAss( assLvl )

	-- запоминаем самый значимый уровень для раскраски прямого подчинения
	if not uStack[#uStack].sublevel or uStack[#uStack].sublevel > assLvl then
		uStack[#uStack].sublevel = assLvl
		modStack = true
	end

	-- строим цепочку подчиненности
	local chain = buildChain()

	args.f = 'n' -- полное наименование в именительном падеже

	local text = p._f( frame, args, 1, #args, { assLvl, args['таб'] } )
	p._openLevel( '-', assLvl )

	if args['таб'] then
		global.set( 'UTab', data.Tabs[uStack[1].unit][1] )
		if args['для'] then
			uStack[#uStack].forfor = args['для']
			uStack[#uStack].forTop = true
			modStack = true
		end
--		return fReturn( tab .. resAss
--			.. '<div class="bs-raw-wrap"><div class="bs-tab-wrap"><div class="bs-lvl-'
--			.. assLvl .. '"><p>' .. text  )
		return fReturn( tab .. resAss
			.. '<div class="bs-raw-wrap"><div class="bs-tab-wrap"><div class="bs-ext-assoc-'
			.. assLvl .. '"><p class="bs-int-assoc">' .. text  )
	end
	local page
	if uPage then
		page = ' &nbsp; <small>[' .. mw.site.server .. mw.site.scriptPath .. '/images/ExtFiles/'
			.. string.sub( bsDate, 1, 4 ) .. '.pdf#page=' .. uPage
			.. ' <i class="fa fa-file-o fa-rotate-90" title="Оригинал, стр. ' .. uPage .. '"></i>]</small>'
	else
		page = ''
	end
	return fReturn( tab .. resAss .. '<div class="bs-ext-assoc-' .. assLvl .. '"><div class="bs-int-assoc">' .. text .. chain .. page .. '</div></div>' )
end
p['Группировка'] = p.Assoc

function p.Subord( frame )
	local args = tools.checkargs( frame:getParent().args, {
			'',
		}, true )
-- TODO  Проверка на ошибки
	fStart()
	bsDate = global.get( 'BSDate' )
	uDBmode = global.get( 'UDBmode' )

	local ass = mw.text.trim( mw.ustring.lower( args[1] ) )
	local assLvl = data.Assocs[ass]
	if not assLvl then
		error( 'Неизвестный уровень группировки ' .. ass )
	end

	-- завершение ранее открытых tab
	local tab = closeTabs()

	-- завершение открытых уровней мельче заданного
	local resAss = closeAss( assLvl+1 )

	-- определяем свой уровень
	assLvl = uStack[#uStack].sublevel or assLvl+1

	-- строим цепочку подчиненности
	local chain = buildChain()
	if chain == '' then
		chain = ' / Ставка'
	end

	local page
	if uPage then
		page = ' &nbsp; <small>[' .. mw.site.server .. mw.site.scriptPath .. '/images/ExtFiles/'
			.. string.sub( bsDate, 1, 4 ) .. '.pdf#page=' .. uPage
			.. ' <i class="fa fa-file-o fa-rotate-90" title="Оригинал, стр. ' .. uPage .. '"></i>]</small>'
	else
		page = ''
	end
	return fReturn( tab .. resAss .. '<div class="bs-ext-assoc-' .. assLvl .. '"><div class="bs-int-assoc">' .. 'Непосредственное подчинение' .. chain .. page .. '</div></div>' )
end
p['Подчинение'] = p.Subord

function p._closeTab(saveWrap)
	inFormation = false
	if not uStack[#uStack].ass then
		local res = ''
		while not uStack[#uStack].ass do
			res = res .. p._closeLevel( '' )
		end
		return res .. '<br><span class=error>ОШИБКА! Отсутствует закрывающая скобка записи о корпусе[[Категория:Ошибки скобок]]</span></p></div></div>'
	end
	if saveWrap then
		return '</p></div>'
	end
	return '</p></div></div>'
end

function p.Row( frame )
	local args, hasArg = tools.checkargs( frame:getParent().args, { '', ['для'] = true } )
	if not hasArg then
		error( 'Правильная запись { { Ряд | } }' )
	end
	fStart()
	if args['для'] then
		uStack[#uStack].forfor = args['для']
		uStack[#uStack].forTop = nil
		modStack = true
		if uTab == 'стр' then
			return fReturn( '' )
		end
	end
	return fReturn( closeTabs() )
end


function p.Tab( frame )
	local args = tools.checkargs( frame:getParent().args, {
			''
		} )
	fStart()

	local tabArray = data.Tabs[uStack[1].unit]
	local prevtab = tabArray[uTab]
	local newtabname = mw.text.trim( mw.ustring.lower( args[1] ) )
	local newtab = tabArray[newtabname]
	local newtabclass = tabArray[newtabname .. '-class']
	local res = {}
	if not newtab then
		error( 'Неизвестная колонка ' .. args[1] )
	end
	if prevtab then
		table.insert( res, p._closeTab( newtab == prevtab ) )
		if newtab < prevtab or newtab == prevtab and not newtabclass  then
			error( 'Нарушен порядок колонок (' .. newtab  .. ' за ' .. prevtab .. ')' )
		end
	else
		prevtab = 0
		table.insert( res, '<div class="bs-raw-wrap">' )
	end
	for _ = prevtab+1, newtab-1 do
		table.insert( res, '<div class="bs-tab-wrap hidden-xs">&nbsp;</div>' )
	end
	if newtab ~= prevtab then
		table.insert( res, '<div class="bs-tab-wrap">' )
	end
	local tabfor
	if newtabname == 'для' then
		tabfor = uStack[#uStack].forfor
		if tabfor then
			tabfor = '<p>' .. tabfor
		end
	end

	table.insert( res, '<div class="bs-tab-' .. ( newtabclass or newtab ) .. '">' .. ( tabfor or '' ) )
	global.set( 'UTab', newtabname )
	global.set( 'UTabEmpty', true )
	return fReturn( table.concat( res ) )
end
p['Таб'] = p.Tab

function p.Dump( frame )
	local log = global.get( 'DEBUGLOG' )
	if log then
		global.set( 'DEBUGLOG', nil )
		return table.concat( log, '<br>\n' )
	end
end

function p.Total( frame )

	local args = tools.checkargs( frame:getParent().args, {
			true, true, true, true, true, true, true,
		} )

	fStart()

	local res = { closeTabs() }
	table.insert( res, '<div class="collapse bs-total"><div class=bs-total-stored>Всего ' .. args[1] .. '</div>' )
	table.insert( res, '<div class="bs-raw-wrap">' )
	if #args == 7 then
		args[6] = args[6] .. '</div><div class="bs-total-stored-tab">' .. args[7]
		args[7] = nil
	end
	for i = 2, #args do
		table.insert( res, '<div class="bs-tab-wrap"><div class="bs-total-stored-tab">' .. args[i] .. '</div></div>' )
	end
	table.insert( res, '</div></div>' )
	return fReturn( table.concat( res ) )
end
p['Всего'] = p.Total

function p.Page( frame )
	local page = mw.text.trim( frame:getParent().args[1] )
	global.set( 'UPage', page )
	return '<span id="p' .. page .. '></span>'
end

return p