Модуль:Отексте

Материал из Викитеки — свободной библиотеки
Перейти к навигации Перейти к поиску
Документация Документация

Реализует функциональность шаблона {{Отексте}} средствами Lua. Парсит в словниках шаблоны семейства {{Статья в словнике}}. Может подгружать на страницу секции из ПИ «Страница».

Используемые модули данных

Шаблон:Статья в словнике

Параметры шаблона
  • 1-й и 2-й — названия страниц в двух орфографиях,
  • 3-й параметр (а также 5-й и 7-й, если есть) — номера страниц/столбцов в печатном издании (hard), 4-й (также 6-й и 8-й) — номера страниц в скане (soft). Несколько пар этих параметров используются для сборки статьи из нескольких источников (см., например ЭСБЕ/Россия — единственный пока пример, где используются 7-й и 8-й параметры). Оба параметра (hard и soft) — составные, т. е. могут состоять из нескольких значений через слэш «/»: для hard — страницы/пагинация/том (пагинация — для случаев, когда том имеет несколько пагинаций, пока не используется); для soft — значения from/to/exclude для передачи в <pages>.
    Если параметр soft пустой или отсутствует, но в подмодуле имеется параметр offsets для данного тома — номера soft вычисляются на основе соответствующих номеров hard.

Переменные модуля

Пагинация

Номера страниц вычисляются по формуле: ((hard + correction) / factor) + offset (с округлением вверх).

Параметры
  • hard — номера страниц/столбцов в печатном издании
  • offset — смещение номеров страниц/столбцов книги относительно индексных страниц скана
  • soft — номера страниц в скане, = hard + correction
  • factor — для страниц скана, на которые приходится больше одного номера пагинации (колонки, сканы в разворот и т. п.). Например, при 2 номерах на страницу во всех томах: p.factor = 2
  • correction — корректирующий сдвиг для factor. Если номера идут не в порядке 1-2,3-4,5-6… (здесь достаточно factor), а 1,2-3,4-5… — тогда используется correction = 1

Пример: для номеров 625 и 626 при correction=1, factor=2 и offset=17 получаем страницы 330 и 331; а при correction=0 (это значение по умолчанию) оба раза будет 330.

Модули-расширения

Модуль должен называться как подмодуль этого модуля, например Модуль:Отексте/Документ. И возвращать 3 значения (return params, categories, beforetext). Где первое — таблица параметров в формате data.p главного модуля, второе — таблица с категориями, третье — текст перед шаблоном-шапкой.

Для подключения в шаблоне (в данном примере в Шаблон:Документ) название модуля задаётся в переменной ext:

{{#invoke:Отексте|textinfo|ext=Документ}}
Алгоритм

Запускается Модуль:Отексте. По полученному из шаблона названию вызывается модуль расширения (extpath), которому передается таблица с параметрами шаблона. Он их обрабатывает (в том числе все нестандартные параметры), и возвращает таблицу со стандартным набором данных для сборки Отексте. Далее главный модуль выполняется как обычно, только с другим набором данных.

Отслеживание ошибок

local p = {}

-- Общие данные
local data = {}
local s = mw.loadData ( "Модуль:Отексте/строки" ) -- строки
local isPRS, isDict, isSub, isAuthor, isTrans, isData, isTypeJoin, isNewEdition, isExt -- флаги

--[[ function p.textinfo( frame ) ------------------------------------------------------------------
	Основная возвращаемая функция
--------------------------------------------------------------------------------------------------]]
function p.textinfo( frame )

	initData ( frame ) -- Инициализация

	if not isDict then getDataMain () else getDataExt ( frame ) end
	
	-- Сборка ------------------------------------------------------------------
	local root = mw.html.create()
	root
		:wikitext ( s.PRS[isPRS] )
		:wikitext ( renderQuality() )

	-- Верхний блок ------------------------------------------------------------
		:tag ( "table" )
			:attr ( "id", "headertemplate" )
			:addClass ( "headertemplate ws-noexport" )
			:css ( "clear", "both" )
			:tag ( "tr" )

				-- Ссылка назад
				:tag ( "td" ):addClass ( "header_backlink searchaux" )
					:tag ( "span" ):attr ( "id", "headerprevious" )
						:wikitext ( backarrow ( data.backlink ) )
					:done()
				:done()

				-- Заголовок
				:tag ( "td" ):addClass ( "header_title" )
					:wikitext ( renderTitle () )
					:wikitext ( renderAuthorTrans () )
				:done()

				-- Ссылка вперед
				:tag ( "td" ):addClass ( "header_forelink searchaux" )
					:tag ( "span" ):attr ( "id", "headerprevious" )
						:wikitext ( forearrow ( data.forelink ) )
				:allDone()

		:wikitext ( renderSubNav ( getSubNav () ) ) -- Дополнительная навигация

	-- Примечания (нижний блок) ------------------------------------------------
	root
		:tag ( "table" ):addClass ( "header_notes ws-noexport" )
			:tag ( "tr" ):attr ( "valign", "top" )
				:wikitext ( renderNotes ( frame ) )
				:wikitext ( renderEditions ( frame ) )
			:allDone()
	
		:wikitext ( data.sortKey or "" ) -- Ключ сортировки
		:wikitext ( renderExtraNav ( frame ) ) -- Навигация-мини, НЕОДНОЗНАЧНОСТЬ, ПРЕДУПРЕЖДЕНИЕ
		:wikitext ( renderImgFooter ( frame ) ) -- ИЗОБРАЖЕНИЕ и Footer
		:wikitext ( table.concat ( data.cat ) ) -- Категории
	
	if isDict or isExt then 
		root:wikitext ( data.beforetext .. ( data.pagetext or "" ) ) 
	end

	return tostring ( root )
end

-- Init functions ----------------------------------------------------------------------------------

--[[ function initData ( frame ) -------------------------------------------------------------------
	Получает параметры из frame и frame:getParent()
	Производит первичную обработку параметров и наполняет таблицу data
--------------------------------------------------------------------------------------------------]]
function initData ( frame )
	local page = mw.title.getCurrentTitle()
	local pagename = page.text
	local type, root, edition = parseTitle ( pagename )
	isNewEdition = ( type == 1 or type == 2 )
	isPRS = toBool ( pagename:find ( "/ДО$" ) or pagename:find ( "/ДО/" ) or 
		pagename:find ( " %(ДО%)$" ) or pagename:find ( " %(ДО%)/" ) )
	isSub = page.isSubpage
	data = {
		f = frame.args, -- args
		p = {},
		cat = {}, -- categories
		tempcat = "",
		page = page, -- page data
		pagename = pagename, 
		basename = page.baseText, 
		rootname = page.rootText,
		subname = page.subpageText,
		ns = page.namespace,
		searchname = pagename, -- что искать
		prefix = "",
		suffix = "",
		curr = {}, -- link data
		prev = {}, 
		next = {}, 
		toc = {}, 
		backlink = "", 
		forelink = "", 
		toc_curr = "",
		subnavs = {},
		footer = {},
		wpsearch = false, -- поиск по ВП
		beforetext = "", -- текстовые элементы между шаблоном Отексте и собственно текстом страницы
	}
	isDict = toBool ( is( data.f.dict ) ) -- для словарных статей (данные обрабатываются локальной процедурой getDataExt)
	local nodict = not isDict
	local pargs = frame:getParent().args
	isExt = toBool ( is( data.f.ext ) ) -- для надстроек (данные обрабатываются подключаемым модулем)
	if isExt then
		local extpath = "Модуль:Отексте/" .. data.f.ext
		if mw.title.new( extpath ).exists then
			pargs, data.cat, data.beforetext = require( extpath ).wrapper( frame, pargs ) -- таблица параметров, таблица категорий, текстовая шапка
		end
	end
	data.p = {
		quality = pargs["КАЧЕСТВО"],
		title = is( pargs["НАЗВАНИЕ"] ),
		part = nodict and is( pargs["ЧАСТЬ"] ),  -- nil или строка
		sub = nodict and is( pargs["ПОДЗАГОЛОВОК"] ),
		partsub = nodict and is( pargs["ПОДЗАГОЛОВОКЧАСТИ"] ),
		backlink = is( pargs["ПРЕДЫДУЩИЙ"] ),
		forelink = is( pargs["СЛЕДУЮЩИЙ"] ),
		author = nodict and is( pargs["АВТОР"] ),
		authors = nodict and is( pargs["АВТОРЫ"] ),
		noauthor = pargs["НЕТ_АВТОРА"],
		origLang = mw.ustring.lower( pargs["ЯЗЫКОРИГИНАЛА"] or "" ),  -- nil (не задано) - перев. не известен; '' - нет; или код языка
		origTitle = nodict and is( pargs["НАЗВАНИЕОРИГИНАЛА"] ),
		origSub = nodict and is( pargs["ПОДЗАГОЛОВОКОРИГИНАЛА"] ),
		translator = nodict and is( pargs["ПЕРЕВОДЧИК"] ),
		index = nodict and is( pargs["СОДЕРЖАНИЕ"] ),
		cycle = nodict and is( pargs["ИЗЦИКЛА"] ),
		collection = nodict and is( pargs["ИЗСБОРНИКА"] ),
		created = nodict and is( pargs["ДАТАСОЗДАНИЯ"] ),
		published = nodict and is( pargs["ДАТАПУБЛИКАЦИИ"] ),
		source = nodict and is( pargs["ИСТОЧНИК"] ),
		suppress_source = nodict and pargs["НЕТ_ИСТОЧНИКА"],
		other = is( pargs["ДРУГОЕ"] ),
		lawdeprecated = is( pargs["УТРАТИЛ СИЛУ"] ),
		toc = is( pargs["ОГЛАВЛЕНИЕ"] ),
		subnav = is( pargs["НАВИГАЦИЯ"] ),
		img = nodict and is( pargs["ИЗОБРАЖЕНИЕ"] ),
		imgDesc = nodict and is( pargs["ОПИСАНИЕИЗОБРАЖЕНИЯ"] ),
		editions = is( pargs["РЕДАКЦИИ"] ),
		disambig = is( pargs["НЕОДНОЗНАЧНОСТЬ"] ),
		warning = is( pargs["ПРЕДУПРЕЖДЕНИЕ"] ),
		section = is( pargs["СЕКЦИЯ"] ),
		license = is( pargs["ЛИЦЕНЗИЯ"] ),
		pages = pargs["СТРАНИЦЫ"], -- ЭСБЕ
		wp = is( pargs["ВИКИПЕДИЯ"] ),
		ws = is( pargs["ВИКИТЕКА"] ),
		commons = is( pargs["ВИКИСКЛАД"] ),
		sortKey = is( pargs["КЛЮЧ"] ),
		category = is( pargs["КАТЕГОРИЯ"] ),  -- только для словарных статей (одна буква, для переопределения алфавитных категорий)
		categories = is( pargs["КАТЕГОРИИ"] ),  -- список через тильду ("Категория 1~Категория 2")
	}
	mw.logObject(pargs)
	--end
	-- категории
	if data.p.categories then
		local _cat = mw.text.split (data.p.categories, "%s*~%s*")
		for i, v in ipairs(_cat) do table.insert ( data.cat, '[[Категория:' .. v .. ']]' ) end
	end
	data.toc_choice = { 
		data.p.author, 
		data.p.index, 
		data.p.cycle, 
		data.p.collection, 
		data.basename, 
		data.rootname, 
		data.p.translator 
	}
	if isNewEdition then data.toc_choice[6] = root .. "/" .. edition end
	data.templates = { -- шаблоны словников
		"ЭСБЕ/Статья", 
		"[Сс]татья в словнике%d?", 
		"[Сс]татья в словнике ТСД", 
		"[Тт]сдс", 
		"[Tt]sds", 
		"2[ОO] Статья в словнике" -- не используется??
	}
	data.override = data.f.override
	data.title = data.p.title
	if data.subname == "ДО" then 
		data.searchname, data.suffix = data.basename, "/" .. data.subname
	end
	if isDict then 
		local edition = ""
		data.prefix = data.rootname .. "/"
		if data.prefix ~= pagename then 
			edition = pagename:match ( "^" .. escapePattern ( data.prefix ) .. "([^/]+)/" ) or "" 
			if edition == "ДО" or edition == "ВТ" then 
				data.prefix = data.prefix .. edition .. "/" 
			end
			data.searchname = mw.ustring.gsub ( data.searchname, "^" .. escapePattern ( data.prefix ), "" )
			data.searchname = mw.ustring.gsub ( data.searchname, "/.+$", "" )
		end
		data.beforetext = "<div class='text' style='max-width:100%'><div class='innertext'>"
	end
	local ignoreLang = { 
		[""] = true, 
		["ru"] = true, 
		["rus"] = true, 
		["ru-old"] = true, 
		["русский"] = true 
	}
	if not ignoreLang[data.p.origLang] then isTrans = true end
	isData = data.p.index or data.p.cycle or data.p.collection or data.p.created or 
		data.p.published or ( data.p.source and not data.p.suppress_source )
end

-- Getting functions -------------------------------------------------------------------------------

--[[ function getDataMain () -----------------------------------------------------------------------
	Получение ссылок и пр. данных для обычного режима Отексте
--------------------------------------------------------------------------------------------------]]
function getDataMain ()
	local toc = data.p.toc -- параметр ОГЛАВЛЕНИЕ
	local tmpname, tmpsuffix = data.searchname, data.suffix
	local tocnum = tonumber ( toc )
	if tocnum then -- ОГЛАВЛЕНИЕ = число
		-- использовать BASEPAGENAME вместо PAGENAME для навигации
		if tocnum > 20 then
			-- подняться на уровень выше и остаться там
			data.searchname, data.suffix = data.basename, ""
			tocnum = tocnum - 20
		elseif tocnum > 10 then
			-- получить данные и вернуться к SUBPAGENAME
			data.searchname, data.suffix = data.basename, "/" .. data.subname
			tocnum = tocnum - 10
		end
		-- число в оглавление
		if tocnum == 0 then
			toc = {}
			if data.p.cycle then table.insert ( toc, data.p.cycle ) end
			if data.p.collection then table.insert ( toc, data.p.collection ) end
			if data.p.index then table.insert ( toc, data.p.index ) end
			table.insert ( toc, data.p.author )
		else
			toc = { data.toc_choice[tocnum] }
		end
	elseif toc then -- ОГЛАВЛЕНИЕ = строка
		toc = { toc }
	end
	if toc then -- получение данных
		data.toc = toc
		getLinks(); mw.logObject(data.curr,"data.curr")
		-- если не указан параметр НАЗВАНИЕ, использовать текст из оглавления
		if data.curr[1] then data.title = data.title or data.curr[1] elseif is( data.tempcat ) then addCat( data.tempcat ) end
	end
	data.backlink = is( data.backlink ) or data.p.backlink  -- if all else fails
	data.forelink = is( data.forelink ) or data.p.forelink
	data.searchname, data.suffix = tmpname, tmpsuffix
end

--[[ function getDataExt ( frame ) -----------------------------------------------------------------
	Получение ссылок и пр. данных для словарных статей
--------------------------------------------------------------------------------------------------]]
function getDataExt ( frame )
	-- инициализация
	local name = data.searchname
	local dictname = data.f.dict; local dict
	if dictname == "generic" then dictname = data.rootname end -- префикс
	local dictpath = "Модуль:Отексте/" .. dictname
	if mw.title.new ( dictpath ) .exists then
		dict = mw.loadData ( dictpath )
	else
		addCat ( s.moduleNotFound ) return
	end
	local function split (str, sep) return mw.text.split (str or "", sep or "/") end

	-- Общие данные -----------------------------------------------------------------------------------
	data.p.noauthor = dict.noauthor[isPRS]
	data.override = dict.override[isPRS]

	-- категории и ключ сортировки
	if dict.maincat then addCat ( dict.maincat[isPRS] ) end
	if dict.alphacat then
		local alphacat = mw.ustring.upper ( mw.ustring.match ( data.p.category or name, "%w" ) )
		if dict.alphacorrect then alphacat = dict.alphacorrect[alphacat] or alphacat end -- коррекция алфавитной категории
		addCat ( dict.alphacat[isPRS] .. alphacat .. "]]" )
	end
	if data.p.wp then
		local wp = mw.ustring.lower ( data.p.wp ); local iw = string.match ( wp, "^:?(%w%w):" ) or ""
		addCat ( string.format ( s.cat.wpLink[iw] or s.cat.wpLink[""], dictname ) )
	end
	if data.p.ws then
		local ws = mw.ustring.lower ( data.p.ws ); local iw = toBool ( string.find ( ws, "^:" ) )
		if iw then iw = string.match ( ws, "^:(%w%w):" ) or string.match ( ws, "^:(категория):" ) or iw end
		addCat ( string.format ( s.cat.wsLink[iw] or s.cat.wsLink[true], dictname ) )
	end
	if data.p.commons then addCat ( string.format ( s.cat.cLink, dictname ) ) end
	data.sortKey = frame:preprocess ( "{{DEFAULTSORT:" .. 
		mw.ustring.upper ( mw.ustring.gsub ( data.p.sortKey or name, "[%s%p]", "" ) ) .. "}}" )
	
	-- поиск по ВП
	data.wpsearch = dict.wpsearch 
	
	-- ссылка на словник
	local other = "[[Файл:Brockhaus Lexikon.jpg|20px|link=]] [[" .. 
		( dict.wordlist[isPRS] or dict.wordlist.default ) .. "|" .. s.wordlist[isPRS] .. "]]"
	
	data.beforetext = dict.beforetext or data.beforetext

	-- Источник ---------------------------------------------------------------------------------------
	local itemdata = {}; local _body
	if dict.type == "split" then _body = { dict[data.rootname] }
	elseif dict.type == "join" then _body = dict.body; isTypeJoin = true; 
	else _body = { dict }
	end
	
	-- Заголовок в двух орфографиях -------------------------------------------------------------------
	if dict.jointitles == 2 then data.doubletitle = true end
	
	-- Получение данных -------------------------------------------------------------------------------
	for _, v in ipairs ( _body  ) do
		if data.curr[1] then data.curr = {} end -- для повторного использования
		data.backlink = ""; data.forelink = ""  -- "
		 -- ссылка на словник --------------------------------------------------
		local function link2wl(t)
			local n = t:match ( "/([^/]+)$" );
			local title = "раздел " .. n; if v.wordlists[n] then title = v.wordlists[n][isPRS] or title end
			return "[[" .. ( v.listroot[isPRS] or v.listroot.default ) .. n .. "|" .. title .. "]]", n
		end
		-- отбор разделов для поиска -------------------------------------------
		local toc = {}; local _list = v.listnum; local _name = name
		local hasRuDiacritics = mw.ustring.find ( _name, "[Йй]" )
		if not ( hasRuDiacritics and hasRuDiacritics < 5 ) then _name = mw.ustring.toNFD ( _name ) end
		_name = mw.ustring.gsub ( mw.ustring.gsub ( _name, "[^%w,(]", "" ), "^%p", "" )
		local lang = mw.language.new (mw.language.getContentLanguage().code)
		_name = lang:ucfirst ( lang:lc (_name) ); mw.logObject(_name,"_name")
		for i, _ in ipairs ( _list ) do
			if _name >= _list[i][1] and ( _list[i+1] == nil or _name < _list[i+1][1] ) then
				for _, w in ipairs ( _list[i][2] ) do table.insert ( toc, v.listroot.default .. w ) end
				break
			end
		end
		mw.logObject(toc,"toc")
		if toc[1] then -- непустой список словников
			data.toc = toc; getLinks()
			local _toc, _data = data.toc_curr, data.curr
			if _data[1] then -- страница в словниках найдена
				data.title = data.title or _data[1] -- название статьи
				local function addItemRow (h, s, a)
					local r = { 
						body = v, 
						title = _data[1],
						hard = { raw = h }, 
						soft = { raw = s }, 
						backlink = data.backlink, 
						forelink = data.forelink,
						scans = {},
					}
					r.list, r.num = link2wl ( _toc )
					if v.subtitle then r.subtitle = is(v.subtitle[isPRS]) end
					if a then r.appendshort = dict.appendshort end
					table.insert ( itemdata, r )
				end
				addItemRow (_data[2], _data[3])
				if is( _data[4] ) then addItemRow (_data[4], _data[5], true) end
				if is( _data[6] ) then addItemRow (_data[6], _data[7], true) end
			else
				data.title = data.title or data.searchname -- название статьи
				--table.insert ( itemdata, { body = v, list = link2wl ( _toc ) } )
			end
		end
	end
	if #itemdata == 0 and is( data.tempcat ) then addCat( data.tempcat ) end

	-- Обработка данных ------------------------------------------------------------------------------
	for i, v in ipairs ( itemdata ) do
		local d = v.body
		addCat ( d.cat ); if not is( v.hard.raw ) then addCat ( d.catnopage ) end
		
		-- номера страниц
		-- mw.log("v.hard.raw:",mw.dumpObject(v.hard.raw), "v.soft.raw:",mw.dumpObject(v.soft.raw))
		v.hard.s, v.pagination, v.volume = unpack ( split ( v.hard.raw ) )
		v.soft.from, v.soft.to, v.soft.skip = unpack ( split ( v.soft.raw ) )
		v.hard.from = tonumber ( v.hard.s:match ( "%d+" ) ) or 0
		v.hard.to = tonumber ( v.hard.s:match ( "[-—]%s*(%d+)" ) ) or v.hard.from
		v.soft.from = tonumber ( v.soft.from ) -- строковое значение в число
		v.pagination = is ( v.pagination )
		if v.volume then v.appendshort = false end

		-- номер тома
		if not is( v.volume ) then
			if d.splitvolume then 
				v.volume = v.num:gsub ( "%-%d+$", "" ); v.scanvol = v.volume;
			else
				local volTable = d.wl2volume or d.page2volume
				if volTable then -- если есть таблица соответствия словник/том
					local x
					if not d.wl2volume and d.page2volume then x = v.hard.from else x = v.num end
					for _, w in ipairs ( volTable ) do
						local _from, _to = w.from, w.to
						if type ( x ) == "number" then _from, _to = tonumber ( _from ), tonumber ( _to ) end
						if x >= _from and x <= _to then 
							v.volume = w.volume; v.scanvol = w.scan or v.volume; break 
						end
					end
				else
					v.volume = v.num; v.scanvol = v.num -- нет таблицы, словник = том
				end
			end
		end
		
		-- ссылки на сканы и индекс
		local _volume = d.volumes[v.volume] or {}; addCat ( _volume.cat )
		--local us; if _volume.usesource then us = _volume.usesource end
		-- us = 2
		local offsets = _volume.offsets; if v.pagination then offsets = _volume.paginations[v.pagination].offsets end
		local factor = _volume.factor or d.volumes.factor or d.factor or dict.factor or 1
		local correction = _volume.correction or d.volumes.correction or d.correction or dict.correction or 0
		mw.logObject(v.hard,"v.hard")
		
		local isScanPages = toBool ( _volume.scanpages or dict.scanpages )
		local isManual = toBool ( v.soft.from )
		local isSources = toBool ( _volume.sources or _volume.map )
		local isOffsets = toBool ( offsets )
		
		local _scan = _volume.linkdata or _volume.scan or {}; mw.logObject(_scan,"_scan")
		local function fillScanData ( scan )
			local sc = scan or _scan; mw.logObject(sc,"sc")
			local id, pid, title = sc[1], sc[2], sc[3]
			if type(title) ~= "string" then title = s.scan[isPRS] end
			local pattern = type (dict.scanpat) == "table" and dict.scanpat[pid]
			local isLink = id and pattern
			return id, title, pattern, isLink
		end
		local _id, _title, _pattern, isLink = fillScanData ()
		
		local _index, _linkdata, _scan4index
		
		if isScanPages then -- постраничные сканы
			v.soft.from = v.hard.from; v.soft.to = v.hard.to
			_id, _title, _pattern, isLink = fillScanData(); mw.logObject(_scan,"_scan  #1")
			if isLink and v.soft.from then 
				local link = string.format ( _pattern, _id, v.soft.from, _title )
				local filename = mw.ustring.match ( link, "%[%[:?([^%[%]|]+)|.+%]%]" ); mw.logObject(filename,"filename")
				if is( filename ) and mw.title.new(filename).file.exists then table.insert ( v.scans, link ) end
			end

		elseif isManual then -- номера страниц скана берутся из словника
			v.soft.to = tonumber ( v.soft.to ) or v.soft.from
			_id, _title, _pattern, isLink = fillScanData(); mw.logObject(_scan,"_scan  #2")
			if isLink then table.insert ( v.scans, string.format ( _pattern, _id, v.soft.from, _title ) ) end
			
		elseif isSources then 
			local _sources = _volume.sources or { _volume }
			--mw.logObject(_sources,"_sources")
			if _sources then
				for i, w in ipairs(_sources) do
					local map = w.map; if v.pagination then map = w.paginations[v.pagination].map end
					if ( w.linkdata or w.index or w.scan ) and map then
						local map_len = 0; for i in ipairs ( map ) do map_len = i end
						local pagenum, hstart, sstart
						factor = w.factor or factor; correction = w.correction or correction
						for j = map_len, 1, -1 do
							x = map[j]
							if v.hard.from >= x[1] then
								hstart = x[1]; sstart = x[2]; break
							end
						end
						if hstart and sstart and sstart > 0 then
							mw.logObject(hstart,"hstart"); mw.logObject(sstart,"sstart")
							pagenum = sstart + math.floor ((v.hard.from - hstart + correction) / factor)  -- расчёт номера страницы скана
							if (w.linkdata or w.scan) and pagenum then 
								_id, _title, _pattern, isLink = fillScanData( w.linkdata or w.scan )
								mw.logObject(_scan,"_scan #3")
								if isLink then table.insert ( v.scans, string.format ( _pattern, _id, pagenum, _title ) ) end
							end
							if (w.index or w.scan) and not _index then
								v.soft.from = pagenum
								v.soft.to = sstart + math.floor ((v.hard.to - hstart + correction) / factor)
								_index = w.index; _linkdata = w.linkdata or w.scan; _scan4index = false
							end
						else
							v.soft.from = nil; v.soft.to = nil
						end
					end
				end
			end
			
		elseif isOffsets then
			local offset = 0
			for _, w in ipairs ( offsets ) do
				if v.hard.from >= w.from and v.hard.from <= w.to then
					offset = w.offset; factor = w.factor or factor; correction = w.correction or correction; scansource = w.scansource; break
				end
			end
			if offset then
				v.soft.from = math.ceil (( v.hard.from + correction ) / factor ) + offset; if v.soft.from == 0 then v.soft.from = nil end
				v.soft.to = v.soft.to or math.ceil (( v.hard.to + correction ) / factor ) + offset
			else
				v.soft.from = nil; v.soft.to = nil
			end
			local _s = _scan; if tonumber ( scansource ) then _s = _scan[scansource] or _s end; mw.logObject(_s,"_s")
			_id, _title, _pattern, isLink = fillScanData( _s )
			mw.logObject(_scan,"_scan  #4"); mw.logObject(isLink,"isLink"); mw.logObject(_id,"_id"); mw.logObject(_pattern,"_pattern")
			if isLink and v.soft.from then table.insert ( v.scans, string.format ( _pattern, _id, v.soft.from, _title ) ) end
		end
		mw.logObject(offset, "offset"); mw.logObject(v.soft.from, "v.soft.from"); mw.logObject(v.soft.to, "v.soft.to")

		-- префикс ссылок на скан
		local scantitle = ""; local b = _volume.short or _volume; mw.logObject(v.volume,"v.volume")
		if v.appendshort then
			scantitle = v.hard.s
		else
			if b[isPRS] then scantitle = ( b.prefix or "" ) .. b[isPRS] .. ( b.suffix or "" )
			elseif is( v.volume ) then scantitle = " т. " .. ( tonumber (v.volume) or v.volume )
			end
			if is( v.hard.s ) then scantitle = scantitle .. ", " .. ( d.rnum or dict.rnum ) .. v.hard.s end
		end

		-- ссылка на индекс
		local indexfile
		if dict.indexpat then
			local id, pattern
			_index = _index or _volume.index; _linkdata = _linkdata or _scan; _scan4index = dict.scan4index
			if _index then
				if type(_index) == "table" then id, pattern = _index[1], _index[2] or 1
				elseif type(_index) == "string" then id, pattern = _index, 1
				elseif type(_index) == "number" then id, pattern = "", _index
				elseif type(_index) == "boolean" and type(_linkdata) == "table" then id, pattern = _linkdata[1], _linkdata[2] 
				end
			elseif _linkdata and _scan4index then id, pattern = _linkdata[1] or "", _linkdata[2] or 1
			else id, pattern = v.scanvol, 1
			end

			if type(dict.indexpat) == "table" then
				indexfile = (string.format ( dict.indexpat[pattern], id ))
			elseif type(dict.indexpat) == "string" then
				indexfile = (string.format ( dict.indexpat, id ))
			end
			mw.logObject(indexfile,"indexfile")

			if exists ( "Индекс:" .. indexfile ) then
				v.index = "[[Индекс:" .. mw.text.trim(indexfile) .. "|" .. s.index[isPRS] .. "]]"
				mw.logObject(v.index,"v.index #")
				
				table.insert ( v.scans, v.index )
			end
		end
		
		v.scan = scantitle
		mw.logObject(v.scans,"v.scans")
		if #v.scans > 0 then v.scan = v.scan .. " ( <span class=plainlinks>" .. table.concat ( v.scans, " · " ) .. "</span> )" end
			
		-- текст статьи из индекса
		if indexfile then 
			if ( dictname == "ЭСБЕ" ) and isPRS then
				if data.p.pages == "нет" then data.p.pages = false else data.p.pages = true end -- ЭСБЕ/ДО
			end
			
			if ( data.p.pages or dict.transclude ) and is( v.soft.from ) then
				local _text; local _section = data.p.section or name
				if dict.section_mod then _section = _section .. (dict.section_mod[isPRS] or "") end;mw.logObject(_section,"_section")
				if dict.transclude_onlysection then
					_text =  frame:extensionTag ( "pages", "", { 
						index = indexfile, 
						from = v.soft.from, 
						to = v.soft.to or v.soft.from, 
						exclude = v.soft.skip or "", 
						onlysection = _section,
					} )
				else 
					_text =  frame:extensionTag ( "pages", "", { 
						index = indexfile, 
						from = v.soft.from, 
						to = v.soft.to or v.soft.from, 
						exclude = v.soft.skip or "", 
						fromsection = _section, 
						tosection = _section,
					} )
				end
				if is( _text ) then addToPage ( _text ); mw.logObject(_text,"_text") else addCat ( s.cat.noText ) end
			end
		end
	end
	
	-- Дообработка ------------------------------------------------------------------------------------
	local mainlist
	if #itemdata > 1 and isTypeJoin then
		-- вынос доп. навигации в Subnav
		local subnavs = {}
		local main, to, step = 1, #itemdata, 1; if dict.reversenav then main, to, step = #itemdata, 1, -1 end
		local from = main + step
		for i = from, to, step do 
			if itemdata[i].list ~= itemdata[i-step].list and not itemdata[i].body.skipnav then
				if is( itemdata[i].subtitle ) then 
					itemdata[i].list = mw.ustring.gsub ( itemdata[i].list, "|[^%[%]]+%]%]", "|" .. itemdata[i].subtitle .. "]]" ) 
				end
				table.insert ( subnavs, { itemdata[i].backlink, itemdata[i].forelink, itemdata[i].list } )
			end
		end
		data.subnavs = subnavs
		
		-- исправление осн. навигации
		mainlist = itemdata[main].list
		data.backlink = itemdata[main].backlink; data.forelink = itemdata[main].forelink
		
		-- объединение названий
		if dict.jointitles == 1 then
			local titles = {}; local hash = {};
			for i = main, to, step do
				local t = itemdata[i].title
				if is( t ) and not hash[t] then table.insert ( titles, t ); hash[t] = true end
			end
			if #titles > 0 then data.title = table.concat ( titles, " / " ) end
		end
	elseif #itemdata > 0 then
		data.backlink = itemdata[1].backlink;  data.forelink = itemdata[1].forelink
	end
	if #itemdata > 0 then
		-- убираем повторы индексов
		for i = 2, #itemdata do 
			for j = 1, i-1 do 
				if itemdata[i].index == itemdata[j].index then itemdata[i].index = "" end
			end
		end
		-- добавляем ссылку на первый словник
		other = other .. ": " .. ( mainlist or itemdata[1].list ) .. ". " 
	else
		other = other .. ". "
	end
	local sources = {} -- сборка ссылок на источники
	for i, v in ipairs ( itemdata ) do
		local src = itemdata[i].scan or ""
		-- if is( itemdata[i].index ) then src = src .. " (" .. itemdata[i].index .. ")" end
		if is( src ) then table.insert ( sources, src ) end
	end
	if #sources > 0 then 
		other = other .. "'''" .. s.source[isPRS] .. "''' "  .. table.concat ( sources, "; " )
	end
	data.other = other .. ( getString ( dict.other ) )
end

--[[ function getSubNav () -------------------------------------------------------------------------
	Дополнительная навигация для верхнего колонтитула
--------------------------------------------------------------------------------------------------]]
function getSubNav ()
	if data.subnavs and #data.subnavs > 0 then return data.subnavs end -- уже есть
	local subnav, subnavs = data.p.subnav, {}
	if subnav then
		if subnav:find ( "headertemplate" ) then -- есть шаблон Sub-nav
			for m in subnav:gmatch ( "<table[^>]*>(.-)</table>" ) do
				local z = {}
				for j in m:gmatch ( "<td[^>]*>(.-)</td>" ) do table.insert ( z, j ) end
				z[2], z[3] = z[3], z[2]
				table.insert ( subnavs, z )
			end
		else
			-- заморозка значений
			local b, f = data.backlink, data.forelink; data.backlink = ""; data.forelink = "";
			local tmp1, tmp2, tmp3 = data.searchname, data.prefix, data.suffix;
			if isDict then data.searchname, data.prefix = data.pagename, "" end;
			
			local _toc = mw.text.split ( subnav, "%s*~%s*" )
			for i, v in ipairs( _toc ) do
				local tocnum = tonumber ( v )
				if tocnum then 
					if tocnum > 20 then
						-- подняться на уровень выше и остаться там
						data.searchname, data.suffix = data.basename, ""
						tocnum = tocnum - 20
					elseif tocnum > 10 then
						-- получить данные и вернуться к SUBPAGENAME
						data.searchname, data.suffix = data.basename, "/" .. data.subname
						tocnum = tocnum - 10
					end
					v = data.toc_choice[tocnum] 
				end
				if is( v ) then
					data.toc = { v }; getLinks();
					if #data.curr > 0 then
						table.insert ( subnavs, { data.backlink, data.forelink, linkify ( v ) } )
					end
					mw.logObject(data.curr,"data.curr"); mw.logObject(subnavs,"subnavs")
				end
			end
			data.backlink = b; data.forelink = f;
			data.searchname, data.prefix, data.suffix = tmp1, tmp2, tmp3;
		end
	end
	data.subnavs = subnavs
	return subnavs
end

--[[ function getFooter () -------------------------------------------------------------------------
	Навигация для нижнего колонтитула
--------------------------------------------------------------------------------------------------]]
function getFooter ()
	local footer = mw.clone ( data.subnavs )
	-- Для подстраниц
	local parents = {}
	local temp, temp1, x
	if isSub then
		local parts = mw.text.split ( data.basename, "/" )
		for i, v in ipairs ( parts ) do
			if temp then temp = temp .. "/" .. v else temp = v end
			if temp1 then temp1 = temp1 .. "/" .. v else temp1 = v end
			local x = mw.title.new ( temp )
			if x.exists then 
				table.insert ( parents, "[[" .. temp .. "|" .. temp1 .. "]]" )
				temp1 = nil
			end
		end
		if #parents > 0 then temp = table.concat ( parents, " : " ) else temp = nil end
	end
	local tocnum = tonumber ( data.p.toc )
	local _toc; if tocnum then _toc = data.toc_choice[tocnum] end
	local middlelink = data.override or temp or _toc or toLink(data.p.cycle) or 
		toLink(data.p.collection) or toLink(data.p.index) or 
		toLink(linkify(data.p.author)) or "[[#top|Наверх]]"
	middlelink = linkify ( middlelink )
	table.insert ( footer, 1, { data.backlink or "", data.forelink or "", middlelink } )
	return footer, true
end

--[[ function getLinks () --------------------------------------------------------------------------
	Формирует ссылки на основе данных getLinkData
--------------------------------------------------------------------------------------------------]]
function getLinks ()
	local prefix, suffix = data.prefix, data.suffix
	local link_title, toc_curr
	for i, v in ipairs ( data.toc ) do
		data.tempcat = "" -- очистка временной категории
		-- если используется отдельное оглавление для /ДО
		if mw.ustring.match ( v, "/ДО$" ) and not isNewEdition then 
			data.searchname = data.searchname .. "/ДО"
			suffix = ""
		end
		data.toc_curr = v
		getLinkData ()
		if is( data.curr[1] ) then break end
	end
	if is( data.prev[1] ) then
		link_title = is( data.prev[2] ) or is( data.prev[1] )
		if isPRS then link_title = is( data.prev[3] ) or link_title end
		data.backlink = "[[" .. prefix .. data.prev[1] .. suffix .. "|" .. link_title .. "]]"
	else
		data.backlink = ""
	end
	if is( data.next[1] ) then
		link_title = is( data.next[2] ) or is( data.next[1] )
		if isPRS then link_title = is( data.next[3] ) or link_title end
		data.forelink = "[[" .. prefix .. data.next[1] .. suffix .. "|" .. link_title .. "]]"
	else
		data.forelink = ""
	end
	local link_curr = {}
	if is( data.curr[1] ) then
		link_title = is( data.curr[2] ) or is( data.curr[1] )
		if data.doubletitle and is( data.curr[3] ) then 
			link_title = link_title .. " / " .. data.curr[3] 
		elseif isPRS then 
			link_title = is( data.curr[3] ) or link_title
		end
		link_curr[1] = link_title
		for i = 4, #data.curr do table.insert ( link_curr, data.curr[i] ) end
	end
	data.curr = link_curr
	--addCat ( data.tempcat )
end

--[[ function getLinkData () -----------------------------------------------------------------------
	Parses data.toc for link to given data.pagename
	Returns link data about current, previous and next pages
--------------------------------------------------------------------------------------------------]]
function getLinkData ()
	local name, toc, prefix = data.searchname, data.toc_curr, data.prefix
	local curr, prev, next = {}, {}, {} -- для сбора данных
	data.curr, data.prev, data.next = curr, prev, next -- очистка
	
	if not is( toc ) then data.tempcat = s.cat.indexNone return end
	if type ( toc ) ~= "string" then data.tempcat = s.cat.indexWrongType .. type ( toc ) .. "]]" return end
	toc = toc:match ( "%[%[([^%[%]|#]+)[%]|#]" ) or toc -- очистка названия оглавления
	-- проверка корректности имени оглавления
	local title = mw.title.new ( toc )
	if not title then data.tempcat = s.cat.indexWrongName return end
	-- проверка на редирект
	if title.isRedirect then title = title.redirectTarget end
	x = title:getContent();mw.logObject(title.text,"title.text")
	-- проверка наличия страницы оглавления
	if not x then data.tempcat = s.cat.indexNotFound return end

	-- Подстановка переменных
	x = x:gsub ( "{{PAGENAME}}", title.text )
	x = x:gsub ( "{{НАЗВАНИЕ_СТРАНИЦЫ}}", title.text )
	
	-- Определяем шаблон, используемый в словнике
	local pattern, template
	for i, v in ipairs ( data.templates ) do
		template = mw.ustring.match ( x, "{{%s*(" .. v .. ")%s*|" )
		if template then pattern = v break end
	end

	local pattern1, pattern2, pattern3, pattern4
	if template then
		x = mw.ustring.gsub ( x, "%s*|%s*", "|" )
		pattern1 = "{{%s*" .. pattern .. "%s*|%s*(" .. escapePattern( name ) .. "%s*|[^{}]+)%s*}}"
		pattern2 = "{{%s*" .. pattern .. "%s*|%s*(" .. escapePattern( name ) .. ")%s*}}"
		pattern3 = "{{%s*" .. pattern .. "%s*|%s*([^{}]+)%s*}}"
		pattern4 = "{{%s*Статья в другом словнике%s*|%s*([^{}]+)%s*}}"
	else
		-- маркер-граница списка (в виде шаблона)
		x = x:gsub("{{Начало списка в оглавлении}}", "[[Викитека:Граница списка в оглавлении]]")
		x = x:gsub("{{Конец списка в оглавлении}}", "[[Викитека:Граница списка в оглавлении]]")
		-- лишние секции и заголовки
		x = x:gsub ( "\n== *См%. также *==.*", "" )
		x = x:gsub ( "\n== *Примечания *==.*", "" )
		x = x:gsub ( "\n===?=?=?[^=\n]+===?=?=?",  "" ) -- м.б. ссылки в заголовках
		-- Шаблон {{2О}} в ссылку
		x = x:gsub ( "{{lang|[^|]+|([^{}]+)}}", "%1" )
		x = mw.ustring.gsub ( x, "{{\s*2[ОO]\s*|([^{}]+)}}", "[[%1]]" )
		-- Убираем лишние фигурные скобки перед очисткой шаблонов
		x = x:gsub ( "\n{|", "\n" )
		x = x:gsub ( "\n|}", "\n" )
		x = mw.ustring.gsub ( x, "{{%s*[Oo]ncolor%s*|", "" )
		x = mw.ustring.gsub ( x, "{{%s*[Dd]otted TOC", "" )
		x = x:gsub ( "{{ВАР2?", "" )
		x = x:gsub ( "{{:MediaWiki:Proofreadpage.index.template", "" )
		-- Удаляем оставшиеся шаблоны и пр.
		x = x:gsub ( "%b{}", "" )
		x = x:gsub ( "%[%[[:#][^%[%]]-%]%]", "" )
		x = x:gsub ( "%[%[[^%[%]|#]+#[^%[%]]+%]%]", "" )
		x = x:gsub ( "%[%[[%a-]+: *[^%[%]]-%]%]", "" ) -- Википедия и пр.
		x = x:gsub ( "%[%[Категория: *[^%[%]]-%]%]", "" )
		x = x:gsub ( "%[%[Файл: *[^%[%]]-%]%]", "" )
		x = x:gsub ( "%[%[Изображение: *[^%[%]]-%]%]", "" )
		-- Развертка имен подстраниц
		x = x:gsub ( "%[%[/", "[[" .. toc .. "/" )
		if is( prefix ) then x = x:gsub ( "%[%[ *" .. prefix, "[[" ) end
		
		pattern1 = "%[%[ *(" .. escapePattern( name ) .. " *|[^%[%]]*)%]%]"
		pattern2 = "%[%[ *(" .. escapePattern( name ) .. ") *%]%]"
		pattern3 = "%[%[ *([^%[%]]+) *%]%]"
	end
	x = mw.ustring.gsub ( x, "[%s_]+", " " )
	
	mw.logObject(pattern1,"pattern1")
	mw.logObject(pattern2,"pattern2")
	mw.logObject(pattern3,"pattern3")
	mw.logObject(pattern4,"pattern4")
	
	local offset1, offset2, foundstr = mw.ustring.find ( x, pattern1 )
	if not offset1 then offset1, offset2, foundstr = mw.ustring.find ( x, pattern2 ) end
	if not offset1 then data.tempcat = s.cat.pageNotFound; return end
	
	local x1 = mw.ustring.sub ( x, 1, offset1 )
	local foundstr1 = mw.ustring.match ( x1, ".+" .. pattern3 )
	if not foundstr1 and pattern4 then foundstr1 = mw.ustring.match ( x1, ".+" .. pattern4 ) end
	
	local x2 = mw.ustring.sub ( x, offset2 )
	local foundstr2 = mw.ustring.match ( x2, pattern3 )
	if not foundstr2 and pattern4 then foundstr2 = mw.ustring.match ( x2, pattern4 ) end
	
	if foundstr then 
		curr = mw.text.split ( foundstr, " *| *" ) 
		for i = #curr, 1, -1 do if curr[i]:find ( "=" ) then table.remove ( curr, i ) end end
		mw.logObject(curr,"curr")
		if template then table.insert ( curr, 1, name ) else curr[1] = name end
	end
	if foundstr1 and foundstr1 ~= "Викитека:Граница списка в оглавлении" then
		prev = mw.text.split ( foundstr1, " *| *" ) 
		if template then table.insert ( prev, 1, prev[1] ) end
	end
	if foundstr2 and foundstr2 ~= "Викитека:Граница списка в оглавлении" then
		next = mw.text.split ( foundstr2, " *| *" ) 
		if template then table.insert ( next, 1, next[1] ) end
	end
	data.curr, data.prev, data.next = curr, prev, next
	
end

--[[ function p.getlinkdata( frame ) ---------------------------------------------------------------
	Вспомогательная функция для вызова getLinkData из фрейма (тест)
--------------------------------------------------------------------------------------------------]]
function p.getlinkdata( frame ) 
	local links = getLinkData ( frame.args.name, frame.args.toc )
	local result = {}
	for i, v in pairs ( links ) do
		table.insert( result, "* " .. i .. ": " .. table.concat ( v, " --- " ) )
	end
	return table.concat( result, "\n" )
end

-- Rendering functions -----------------------------------------------------------------------------

--[[ function renderQuality () ---------------------------------------------------------------------
КАЧЕСТВО
--------------------------------------------------------------------------------------------------]]
function renderQuality ()
	local quality = s.q[data.p.quality] or "00%"
	if is( quality ) then 
		addCat ( "[[Категория:" .. quality .. "]]" )
	
		-- render
		local result = mw.html.create( "span" )
		result
			:addClass ( "ws-noexport" ):css ( "display", "none" )
			:tag ( "span" ):attr ( "id", "textquality" ):addClass ( quality )	
		return tostring ( result )
	else
		return ""
	end
end

--[[ function renderTitle () -----------------------------------------------------------------------
НАЗВАНИЕ, ПОДЗАГОЛОВОК, ЧАСТЬ, ПОДЗАГОЛОВОКЧАСТИ
--------------------------------------------------------------------------------------------------]]
function renderTitle ()
	local title = is( data.title ) or s.notitle[isPRS]
	local sub, part, partsub = data.p.sub, data.p.part, data.p.partsub
	if sub then sub = " : " .. sub; addCat(s.cat.pageWithSub) else sub = "" end
	if partsub then partsub = " : " .. partsub; addCat(s.cat.pageWithSub) else partsub = "" end
	if part then 
		if sub and partsub then sub = "" end -- ЧАСТЬ + ПОДЗАГОЛОВОКЧАСТИ - ПОДЗАГОЛОВОК
	end

	-- render
	local result = mw.html.create()
	result
		:tag ( "span" ):attr ( "id", "header_title_text" ):css ( "font-weight", "bold" )
			:tag ( "span" ):attr ( "id", "ws-title" )
				:wikitext ( title )
			:done()
			:wikitext ( sub )
	
	if part then
		result
			:tag ( "span" ):attr ( "id", "header_section_text" )
				:wikitext ( "&nbsp;— " )
				:tag ( "span" ):attr ( "id", "ws-chapter" )
					:wikitext ( part )
				:done()
				:wikitext ( partsub )
	end
	return tostring ( result )
end

--[[ function renderAuthorTrans () -----------------------------------------------------------------
АВТОР(ы), ПЕРЕВОДЧИК
--------------------------------------------------------------------------------------------------]]
function renderAuthorTrans ()
	local author, translator, translatortext
	-- Author
	if data.p.noauthor then author = is( data.p.noauthor ) or ""
	elseif data.p.authors then author = "авторы: " .. data.p.authors
	elseif data.p.author then author = s.author[isPRS] .. linkify ( data.p.author )
	else author = s.unknown_author[isPRS]
	end
	isAuthor = toBool ( is( author ) )
	
	-- Translator
	if isTrans then 
		translator = data.p.translator
		if translator == nil then 
			translatortext = s.unknown_trans[isAuthor][isPRS]
			addCat ( s.cat.unknownTranslator )
		elseif translator == "нет" then translatortext, translator = "", nil
		else translatortext = s.trans[isAuthor]
		end
	end

	-- render
	local result = mw.html.create()
	
	if isAuthor or isTrans then result:tag( "br" ) end
		
	local header_author_text = result
		:tag ( "span" ):attr ( "id", "header_author_text" )
			:tag ( "span" ):attr ( "id", "ws-author" )
				:wikitext ( author )
	
	if not isDict then header_author_text:css ( "font-style", "italic" ) end
	
	if isTrans then header_author_text:wikitext ( translatortext ) end
	
	if is( translator ) then
		header_author_text
			:tag ( "span" ):attr ( "id", "ws-translator" )
				:wikitext ( linkify ( translator ) )
	end
	return tostring ( result )
end

--[[ function renderNotes ( frame ) ----------------------------------------------------------------
ЯЗЫКОРИГИНАЛА, СОДЕРЖАНИЕ, ИЗЦИКЛА, ИЗСБОРНИКА, ДАТАСОЗДАНИЯ, ДАТАПУБЛИКАЦИИ, ИСТОЧНИК и ДРУГОЕ
--------------------------------------------------------------------------------------------------]]
function renderNotes ( frame )
	-- Languages
	local ol, os, ot = data.p.origLang, data.p.origSub, data.p.origTitle
	local lang = ""
	if isTrans then 
		local langs = mw.loadData('Модуль:Отексте/transl-lang')
		if not langs[ol] then ol = "un" end
		lang = s.lang[isPRS] .. langs[ol][isPRS]
		if os then os = " : " .. os; addCat(s.cat.pageWithSub) else os = "" end
		if ot then lang = lang .. ". " .. s.orig_title[isPRS] .. "'''" .. ot .. os .. "'''" end
		if data.p.translator then addCat ( s.cat.transFrom .. langs[ol]["cat"] .. "]]" ) end
	end

	-- Notes
	local created, published, source = data.p.created, data.p.published, data.p.source
	
	local coll_pre
	if data.p.cycle and data.p.collection then coll_pre = "сб. " else coll_pre = s.collection[isPRS] end
	
	local publ_pre, dates
	if created and published then publ_pre = ", опубл.: " else publ_pre = "Опубл.: " end
	if created then created = s.created[isPRS] .. created else created = "" end
	if published then published = publ_pre .. published else published = "" end
	dates = created .. published
	if is( dates ) then dates = dates .. ". " else dates = "" end
	
	if data.p.suppress_source then -- параметр НЕТ_ИСТОЧНИКА
		source = ""
	elseif source then 
		source = s.source[isPRS] .. source .. " "
	else
		source = ""
		if data.ns == 0 and not isSub then addCat ( s.cat.noSource ) end
	end

	local other = data.other or data.p.other
	local lawdeprecated = data.lawdeprecated or data.p.lawdeprecated
	-- separators
	if isData or other then if is( lang ) then lang = lang .. ".&nbsp;— " end end
	if isData then 
		if other then other = " • " .. other else other = "" end 
		if lawdeprecated then other = other .. s.lawdeprecated[isPRS] .. lawdeprecated; addCat ( s.cat.lawdeprecated ) end 
	end
		

	local other_sources = require( "Модуль:Другие источники" ).renderOtherSources( frame )
	
	-- render
	local result = mw.html.create( "td" )
		result:wikitext ( lang )
	
	if data.p.index then
		result
			:wikitext ( "''См. ")
			:tag ( "span" ):attr ( "id", "header_contents" )
				:wikitext ( linkify ( data.p.index ) )
			:done()
			:wikitext ( ".'' " )
	end
	
	if data.p.cycle then
		result
			:wikitext ( "''" .. s.cycle[isPRS] .. "«")
			:tag ( "span" ):attr ( "id", "header_cycle_text" )
				:wikitext ( data.p.cycle )
			:done()
			:wikitext ( "»''" )
		
		if data.p.collection then result:wikitext ( ", " ) else result:wikitext ( ". " ) end
	end
	
	if data.p.collection then
		result
			:wikitext ( " ''" .. coll_pre .. "«")
			:tag ( "span" ):attr ( "id", "header_coll_text" )
				:wikitext ( data.p.collection )
			:done()
			:wikitext ( "»''. " )
	end	
	
	result 
		:wikitext ( dates )
		:wikitext ( source )
		:wikitext ( other )
		:wikitext ( other_sources )

	return tostring ( result )
end

--[[ function renderEditions ( frame ) -------------------------------------------------------------
ДО/СО и Редакции
--------------------------------------------------------------------------------------------------]]
function renderEditions ( frame )
	local pagename = data.pagename
	local e = data.f.editions or data.p.editions; if e == "нет" or e == "no" then return "" end
	local isShort = ( e == "0" ) or ( e == "short" ) or ( e == "кратко" )
	local editions = mw.html.create( "td" ):attr ( { align = "right", valign = "top", id = "editions" } )
	
	-- helper functions
	local function addYatButton ( oldspell, newspell )
		local e1, e2, button
		if isPRS then e1, e2 = oldspell, newspell else e1, e2 = newspell, oldspell end
		if exists (e2) then button = s.blueYat[isPRS] else button = s.redYat[isPRS] end
		return button .. e2 .. "|" .. e1 .. s.PRSButtonTip[isPRS]
	end
	
	-- получение данных
	local t, root, edition, sub = parseTitle ( pagename )

	-- обработка
	if t == 1 then
		if not exists ( root ) then return tostring ( editions ) end
		local txt = mw.title.new( root ):getContent()
		txt = txt:match ( "\n(%*.-)\n[^%*]" )
		if not txt then 
			editions:tag ( "div" ):attr ( "id", "editions_toggle" )
				:wikitext ( "[[" .. root .. "|" .. s.editionsHeader[isPRS] .. "]]" )
				:done()
			return tostring ( editions )
		end
		txt = txt:gsub ( "(%[%[/?)([^%[%]|]+)(%]%])", "%1%2|%2%3" )
		txt = txt:gsub ( "%[%[/", "[[" .. root .. "/" )
		txt = frame:preprocess ( txt )
		txt = txt:gsub ("%]%].-\n", "]]\n") -- текст после ссылок
		local egroup = editions
			:tag ( "div" ):attr ( "id", "editions_toggle" )
				:tag ( "p" ):wikitext ( s.editionsHeader[isPRS] ):done():done()
			:tag ( "div"):attr ( "id", "editions_cont" )
				:tag ( "table" )
		local cols = "3"; local alttext = ""
		-- для левой нав. панели
		for m in txt:gmatch ( "%*[^%[%]]-(%[%[[^%[%]]+%]%])" ) do
			local item = egroup:tag ( "tr" )
			local left, right = "", ""; local middle = m
			if m:find ( "^[^|]+ %(ДО%)" ) then left = "[[File:Ять 4.jpg|16px|link=|Дореформенная орфография]]&nbsp;" end
			item:tag ( "td" ):wikitext ( left ):done()
			if isShort or not sub then
				item:tag ( "td" ):wikitext ( middle ):done(); cols = "2"
			else
				middle = m:gsub ( "|", "/" .. sub .. "|" )
				right = m:gsub ( "|[^%[%]]+%]%]", "|(огл.)]]" )
				item:tag ( "td" ):wikitext ( middle ):done()
					:tag ( "td" ):css ( { ["font-size"] = "smaller", ["padding-left"] = "2em" } )
						:wikitext ( right ):done()
			end
			local n = middle:match ( "%[%[([^%[%]|]+)|" ); local e; _, _, e = parseTitle ( n )
			if e then alttext = alttext .. "\n* " .. middle:gsub ( "|[^%[%]|]+%]%]", "|" .. e .. "]]" ) end
		end
		egroup:tag ( "tr" )
			:tag ( "td" ):attr ( "colspan", cols )
				:css ( { ["font-size"] = "smaller", ["text-align"] = "center" } )
				:wikitext ( "([[" .. root .. "|" .. s.editionsList[isPRS] .. "]])" )
		local alt = editions
			:tag ( "div" ):attr ( "id", "altEditions" )
				:css ( "display", "none" )
				:wikitext ( alttext )
		return tostring ( editions )
	elseif t == 2 then 
		editions:wikitext ( addYatButton ( root .. "/ДО/" .. sub, root .. "/ВТ/" .. sub ) )
		return tostring ( editions )
	elseif t == 3 then 
		editions:wikitext ( addYatButton ( root .. "/ДО", root ) )
		return tostring ( editions )
	end
end

--[[ function renderExtraNav ( frame ) -------------------------------------------------------------
Навигация-мини, НЕОДНОЗНАЧНОСТЬ и ПРЕДУПРЕЖДЕНИЕ
--------------------------------------------------------------------------------------------------]]
function renderExtraNav ( frame )
	if data.wpsearch then frame.args.search = data.searchname else frame.args.search = "" end
	local navigation = require( "Модуль:Навигация-мини" ).render( frame )
	local disambig, warning = data.p.disambig, data.p.warning

	-- render
	local result = mw.html.create()
	if navigation or disambig or warning then
		local div = result
			:tag ( "div" ):addClass ( "ws-noexport searchaux" )
				:css ({ 
					["border-bottom"] = "1px solid #a88", 
					["background-color"] = "#fafaff", 
					["font-size"] = "0.9em", 
					["margin-top"] = "0px" 
				})
		
		if warning then
				div:tag ( "span" ):css ({ 
					["color"] = "red", 
					["font-weight"] = "bold" 
				})
				:wikitext ( warning )
		else
			if disambig then 
				div:wikitext ( "[[Файл:Disambig.svg|100x13px|link=" .. 
					disambig .. "]]&nbsp;'''[[" .. disambig .. "|" .. 
					s.disambig[isPRS] .. "]]'''" ) 
			end
			if navigation then div:wikitext ( navigation ) end
		end
	end
	return tostring ( result )
end

--[[ function renderImgFooter ( frame ) ------------------------------------------------------------
Нижний колонтитул и ИЗОБРАЖЕНИЕ
--------------------------------------------------------------------------------------------------]]
function renderImgFooter ( frame )
	local license = ""
	local license_template = s.license[data.p.license or ""] or data.p.license
	if license_template then 
		if exists ( "Шаблон:" .. license_template ) then
			license = frame:expandTemplate { title = license_template }
		else
			addCat ( s.cat.unknownLicense ) 
		end
	end
	frame.args.nocat = "yes"
	local external_links = require( "Module:External links" ).render( frame )
	
	local img = ""
	if data.p.img then 
		img = "[[Файл:" .. data.p.img .. "|right|180px"
		if data.p.imgDesc then 
			img = img .. "|thumb|" .. data.p.imgDesc  .. "]]"
		else
			img = img .. "]]" 
		end
	end
	
	-- render
	local result = mw.html.create()
	result
		:tag ( "div" ):attr ( "id", "ws-footer" ):css ( "display", "none" )
			:tag ( "br" ):attr ( "clear", "all" ):done()
			:newline()
			:wikitext ( license )
			:wikitext ( external_links )
			:wikitext ( renderSubNav ( getFooter() ) ) -- Нижний колонтитул
		:done()
		:tag ( "br" ):attr ( "clear", "all" ):done()
		:wikitext ( img )
	return tostring ( result )
end

--[[ function renderSubNav ( tbl, footer ) ---------------------------------------------------------
Дополнительная навигация для Sub-nav и Footer
--------------------------------------------------------------------------------------------------]]
function renderSubNav ( tbl, footer )
	local result = mw.html.create()
	if #tbl > 0 then
		--if footer then result:tag ( "br" ):done() end
		for i, v in ipairs ( tbl ) do
			local tr = result
				:tag ( "table" ):addClass ( "headertemplate" )
					:css ( "margin-top", "-1px" )
					:tag ( "tr" )
			if footer then tr:css ( "line-height", "120%" ) else tr:css ( "line-height", "100%" ) end
			tr
				:tag ( "td" ):addClass ( "header_backlink searchaux" )
					:css ({ 
						["width"] = "30%", 
						["overflow"] = "hidden", 
						["text-align"] = "left" 
					})
					:wikitext ( backarrow (v[1]) )
				:done()
				:tag ( "td" ):addClass ( "header_title" )
					:css ({
						["width"] = "40%", 
						["overflow"] = "hidden", 
						["text-align"] = "center", 
						["font-size"] = "90%" 
					})
					:wikitext ( v[3] )
				:done()
				:tag ( "td" ):addClass ( "header_forelink searchaux" )
					:css ({ 
						["width"] = "30%", 
						["overflow"] = "hidden", 
						["text-align"] = "right" 
					})
					:wikitext ( forearrow (v[2]) )
		end
	end
	return tostring ( result )
end

-- Helper functions --------------------------------------------------------------------------------

-- проверка переменной, возврат её или nil если пустая
function is ( var ) if ( var == '' or var == nil ) then return nil else return var end end

function exists (title) local t = mw.title.new (title) if t then return t.exists end end

-- возврат действительного true/false
function toBool ( val ) return not not val end

-- добавление категории в data.cat
function addCat ( cat ) if is(cat) then table.insert( data.cat, cat ) end end

function addToPage ( val )
	if data.pagetext then
		data.pagetext = data.pagetext .. s.append[isPRS] .. tostring ( val ) 
	else
		data.pagetext = tostring ( val )
	end
end

-- анализ названия страницы по критериям именования редакций
function parseTitle ( name )
	-- формат 1: Название/Редакция (ДО)/Подстраницы
	local r, e, f, s = name:match ( "^([^/]+%b())/([^/]+ (%b()))/(.+)" ) 
	if not f then r, e, f, s = name:match ( "^([^/]+%b())/([^/]+ (%b()))$" ) end
	if not f then r, e, f, s = name:match ( "^([^/]+)/([^/]+ (%b()))/(.+)" ) end
	if not f then r, e, f, s = name:match ( "^([^/]+)/([^/]+ (%b()))$" ) end
	if f and string.find ( "(ДО)(СО)(ВТ)(ВТ:Ё)(ВТ:У)", f ) then 
		return 1, r, e, s 
	else
		-- формат 2: Префикс/ДО/Название (для сл. статей)
		r, f, e, s = name:match ( "^([^/]+)(/([^/]+)/)(.+)" ) 
		if f and string.find ( "/ДО/ВТ/", f ) then 
			return 2, r, e, s 
		else
			-- формат 3: Название/ДО (old style)
			r, e = name:match ( "^(.+)(/ДО)$" ) 
			if e then 
				return 3, r, e 
			elseif exists ( name .. "/ДО" ) then 
				return 3, name, "" 
			else
				return 0
			end
		end
	end
end

-- escape certain characters in regex patterns
function escapePattern( pattern_str )
	return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" );
end

-- преобразует строку в ссылку, добавляя [[]] при необходимости
-- upd 02.10.2018: ссылка не добавляется, если строка начинается с ~
function linkify ( link )
	if not is( link ) then return end
	if link:find ("^~%s*") then
		link = link:gsub ("^~%s*", "")
	else
		if not link:find ("%[%[") then link = "[[" .. link .. "]]" end
	end
	return link
end

-- добавляет стрелку для ссылки назад
function backarrow ( link )
	if is( link ) and type( link ) == "string" then return "← " .. link end
end

-- добавляет стрелку для ссылки вперед
function forearrow ( link )
	if is( link ) and type( link ) == "string" then return link .. " →" end
end

-- извлечение ссылки из строки (первой, если несколько)
function toLink ( str ) return string.match ( str or "", "(%[%[[^%[%]]+%]%])" ) end

-- проверяет тип переменной и возвращает строковое значение
function getString ( val )
	if type ( val ) == "table" then
		return val.default or val[isPRS] or val[not isPRS] or ""
	elseif type ( val ) == "nil" then
		return ""
	else
		return tostring ( val )
	end
end

function screen ( str )
	if type (str) == "string" then return str:gsub ( "|", "@" ) else return str end
end

function unscreen ( str )
	if type (str) == "string" then return str:gsub ( "@", "|" ) else return str end
end

----------------------------------------------------------------------------------------------------

return p