Модуль:Обработка объектов

Материал из Справочника наблюдателя
Перейти к:навигация, поиск

(i)      Описание модуля[]

Документация отсутствует!


local p = {}
-- local Frame, Args

local global = mw.ext.luaglobal
local cargo = mw.ext.cargo
-- local ObjTab = global.get( 'ObjTab' ) or {}
local Region = global.get( 'RegionCode' )
local GeoParam = global.get( 'GeoParam' )

global.strict( true )

-- const
local categories = { ['УИК'] = 1, ['Власти'] = 2, ['Сервисы'] = 3, } -- Номера неважны
local uikList = { 'номер', 'округ', 'адрес', 'телефон' ,'границы' }
local othList = { 'метка', 'наименование', 'адрес', 'телефон', 'сайты', 'примечание' }


local outRes = { '' }  -- 1 строка для сообщений об ошибках
local Err = {}
local function outputErr( s )
	Err[#Err+1] = '<p class="error">' .. s .. '</p>'
end
local function outputInfo( s )
	Err[#Err+1] = s
end
local function makeOut( frame )
	outRes[1] = table.concat( Err )
	return frame:preprocess( table.concat( outRes, '\n' ) )
end


--------------------------------------------------- |Collect (Шаблон:Объединение)|
-- Используется в тех случаях, когда на странице кампании необходимо объединить
-- несколько частных кампаний. Каждый параметр шаблона записывается в виде:
-- «обозначение~страница» или просто «страница» (обозначение нужно исключительно
-- для ссылок на переход к редактированию перед таблицей)

function p.Collect( frame )
	local args = frame:getParent().args
	local pageCollect, abbrCollect = {}, {}
	for _, page in ipairs( args ) do
		page = mw.text.trim( page )
		if page ~= '' then
			local abbr, name = string.match( page, '^(.+)%s*~%s*(.+)$' )
			if not abbr then
				abbr = string.match( page, ':([^:]+)$' )
				if not abbr then
					abbr = page
				end
				name = page
			end
			if #abbr > #name then
				abbr, name = name, abbr
			end
			table.insert( pageCollect, name )
			table.insert( abbrCollect, abbr )
		end
	end
	if #pageCollect ~= 0 then
		global.set( 'PagesCollection', pageCollect )
		global.set( 'PagesAbbrCollection', abbrCollect )
	end
end



------------------------------------------------------------------------ COLLECT

-- на странице кампании собирается всегда по категории. База — всегда имя
-- кампании (либо Сборка, либо умолчание)
-- на странице Объектов сборка без категорий, а База может быть любой

-- base — параметр шаблона «Сборка!»
-- ctg — извлекаемая категория
local function makeCollection( ctg )
	local base = global.get( 'ObjCollection' )
--	outputInfo( '<br>global «' .. base .. '»')
	local basename = mw.title.getCurrentTitle().baseText
	if ( base or '' ) == '' then
		base = basename
	elseif base == '-:' then
		base = mw.ustring.gsub( basename, ':[^:]+$', '' )
--		outputInfo( '<br>base-mod «' .. base .. '»')
	end
	local collectPage = mw.title.new( base .. '/Сборка' )
	local bases, subs = {}, {}
	if collectPage.id ~= 0 then
		local content = mw.text.trim( collectPage:getContent() or '' )
		content = mw.text.split( content, '[\t\f ]*[\r\n]+[\t\f ]*' )
		for _, val in pairs( content ) do
			if val ~= '' then
				local z = mw.ustring.sub( val, 1, 1 )
				if val == '/' then
					subs[#subs+1] = ''
				elseif z == '/' then
					subs[#subs+1] = val
				elseif z == ':' then
					bases[#bases+1] = base .. val
				else
					bases[#bases+1] = val
				end
			end
		end
		if #bases == 0 and #subs == 0 then
			outputErr( 'Страница «' .. collectPage .. '» не содержит данных' )
		end
	end
	if #bases == 0 then
		bases = { base }
	end
	if ( ctg or '' ) ~= '' then
		subs = { '/' .. ctg }
	elseif #subs == 0 then
		subs = { '/УИК', '/Власти', '/Сервисы' }
	end
	local collection = {}
	for _, b in ipairs( bases ) do
		for _, s in ipairs( subs ) do
			collection[#collection+1] = b .. s
		end
	end
	return collection, base
end


---------------------------------------------------------------------------- csv
local function csv( ObjTab )
	if #ObjTab == 0 then
		return nil
	end

	local function dQ (s)
		return mw.ustring.gsub ( mw.text.decode( s ), '"', '""')
	end

	local fieldsSeq = {
		label           = { 10,  'метка' },
		number          = { 20,  'номер' },
		gas				= { 30,  'ГАС' },
		temp			= { 40,  'временный' },
		name			= { 50,  'наименование' },
		mun				= { 60,  'МО' },
		district		= { 70,  'округ' },
		koib			= { 80,  'КОИБ' },
		members			= { 90,  'ПРГ' },
		room_addr		= { 100, 'адрес (дом)' },
		room_org		= { 110, 'адрес (орг)' },
		room_geo__lat	= { 120, 'адрес (широта)' },
		room_geo__lon	= { 130, 'адрес (долгота)' },
		room_geo_p		= { 140, 'адрес (точность)' },
		room_geo_s		= { 150, 'адрес (показ)' },
		room_phone		= { 160, 'телефон' },
		office_addr		= { 170, 'адрес офиса (дом)' },
		office_org		= { 180, 'адрес офиса (орг)' },
		office_geo__lat	= { 190, 'адрес офиса (широта)' },
		office_geo__lon = { 200, 'адрес офиса (долгота)' },
		office_geo_p	= { 210, 'адрес офиса (точность)' },
		office_geo_s	= { 220, 'адрес офиса (показ)' },
		office_phone	= { 230, 'телефон офиса' },
		border			= { 240, 'границы' },
		quantity		= { 250, 'численность' },
		x_turnout		= { 260, '#явка' },
		x_before		= { 270, '#досрочно' },
		x_home			= { 280, '#дома' },
		x_loyal			= { 290, '#власть' },
		site			= { 300, 'сайты' },
		addendum		= { 310, 'примечание' },
	}

	local columns, xc = {}, {}
	for _,row in ipairs( ObjTab ) do
		row._pageID = nil
		for col, value in pairs( row ) do
			if (value or '') ~= '' and not xc[col] then
				xc [col] = true
				columns[#columns+1] = col
			end
		end
	end

	table.sort( columns, function ( a, b )
			return fieldsSeq[a][1] < fieldsSeq[b][1]
		end )


	local x = {}
	for i, col in ipairs( columns ) do
		x[i] = fieldsSeq[col][2]
	end
	local result = { '"' .. table.concat( x, '","' ) .. '"' }
	for _, row in ipairs( ObjTab ) do
		x = {}
		for i, col in ipairs( columns ) do
			x[i] = dQ( row[col] or '' )
		end
		result[#result+1] = '"' .. table.concat( x, '","' ) .. '"'
	end

	return table.concat( result, '\n' )
end


---------------------------------------------------------------------------- tab
local function normUIK (numbUIK)
	return string.gsub ( '' .. numbUIK, '^0*([0-9]+)[%-/]?([0-9]*)$', '%1%2' )
end

local prec_color = { '#CCC', '#3366BB', '#ff9900', 'red' }

local function showAddr ( row, prefix, alt )
	local addr, org, lat, lon, prec, show =
		row[prefix..'_addr'], row[prefix..'_org'], row[prefix..'_geo__lat'],
		row[prefix..'_geo__lon'], tonumber( row[prefix..'_geo_p'] ), row[prefix..'_geo_s']
	local result = addr
	if ( addr or '' ) == '' then
		return ''
	end
	if ( org or '' ) ~= '' then
		result = result .. ' — ' .. org
	end
	if alt then
		result = result .. ' <span style="color:#746963; font-style:italic">(' .. alt .. ')</span>'
	end
	local marker
	if prec > 4 then
		marker = 'pin'
		prec = prec - 4
	else
		marker = 'marker-alt'
	end
	result = result .. '&nbsp;[http://m.nablawiki.ru/?' .. GeoParam
		.. '&addressSpecial=' .. mw.uri.encode( addr, 'PATH' )
	if ( lat or 0 ) ~= 0 then
		result = result .. '&latlon=' .. lat .. ',' .. lon
		prec = prec_color[ prec ]
	else
		prec = 'red'
	end
	result = result .. ' <span class="fas fa-map-' .. marker .. '" style="color:' .. prec
		.. '" title="на карте"></span>]'
	return result
end

local function compositeAddr ( row )
	local addr, org = row['room_addr'], row['room_org']
	local addrO, orgO = row['office_addr'], row['office_org']
	if ( addrO or '' ) == '' then
		return showAddr( row, 'room' )
	end
	if addr ~= addrO then
		return showAddr( row, 'room' ) .. '<br><span style="color:#746963; font-style:italic">'
			.. showAddr( row, 'office' ) .. '</span>'
	end
	if org == orgO then
		return showAddr( row, 'room' )
	end
	return showAddr( row, 'room', orgO )
end

local function compositePhone( row )
	local room, offi = row.room_phone, row.office_phone
	if room == offi or ( offi or '' ) == '' then
		return room or ''
	end
	return room .. '<br><span style="color:#746963; font-style:italic">' .. offi .. '</span>'
end



-- Структура tabC
-- - дополнительные атрибуты заголовка колонки
-- - название колонки
-- - дополнительные атрибуты содержимого колонки
-- - строка (имя поля таблицы) или функция (function(row))


local tabC = {
	['номер'] = {
			'style="width:4en;"',
			'УИK',
			'style="text-align:center; font-weight:bold;"',
			function ( row )
				local s = row.number
				if s == '' then
					return ''
				else
--					local txt = '[https://www.wikiuiki.org/ik/' .. Region .. '-uik-' .. normUIK (s) .. ' ' .. s .. ']';
--					if (row.gas or '') ~= '' then
--						txt = txt .. ' [' .. row.gas .. ' <sup><span style="color:red">§</span></sup>]';
--					end
					local txt = s
					if (row.gas or '') ~= '' then
						txt = '[' .. row.gas .. ' ' .. txt .. ']';
					end
					if (row.temp or '') ~= '' then
						txt = '{{подсказка|<i>' .. txt .. '</i>|временный участок}}'
					end
					return txt
				end
			end,
		},
	['мо'] = {
			'',
			'МО',
			'style="font-size:83%; line-height:95%;"',
			'mun',
		},
	['округ'] = {
			'style="width:3en;"',
			'<small>округ</small>',
			'style="text-align:center;"',
			'district',
		},
	['коиб'] = {
			'style="width:2en;"',
			'<small>KОИБ</small>',
			'style="text-align:center;"',
			'koib',
		},
--  ['временный'] = {
--			'style="width:2en;"',
--  		'<small>{{подсказка|вр.|участок, созданный для временно пребывающих}}</small>',
--			'style="text-align:center;"',
--      },
	['численность'] = {
			'style="width:4en;"',
			'изб.',
			'style="text-align:right;"',
			'quantity',
		},
	['прг'] = {
			'',
			'{{подсказка|ПPГ|число членов комиссии с правом решающего голоса}}',
			'style="text-align:center;"',
			'members',
		},
	['адрес']           = { 'class=unsortable',
							'адрес помещения',
							'style="font-size:92%; line-height:95%;"',
							function ( row )
								return showAddr( row, 'room' )
							end
						  },
	['адреса']          = { 'class=unsortable',
							'адрес помещения, <i>адрес офиса</i>',
							'style="font-size:92%; line-height:95%;"',
							compositeAddr,
						  },
	['телефон']         = { 'class=unsortable',
							'телефон',
							'',
							'room_phone',
						  },
	['телефоны']        = { 'class=unsortable',
							'телефоны',
							'',
							compositePhone,
						  },
	['адрес офиса']     = { 'class=unsortable',
							'адрес офиса',
							'style="font-size:92%; line-height:95%;"',
							function ( row )
								return showAddr( row, 'office' )
							end
						  },
	['телефон офиса']   = { 'class=unsortable',
							'тел. офиса',
							'',
							'office_phone',
						  },
	['границы']         = { 'class=unsortable',
							'границы избирательного участка',
							'style="font-size:83%; line-height:95%;"',
							'border',
						  },
	['метка']           = { 'style="width:3en; border-right:0px;"',
							' ',
							'style="font-size:83%; line-height:95%;"',
							'label',
						  },
	['наименование']    = { '',
							'наименование органа',
							'',
							'name',
						  },
	['сайты']           = { 'class=unsortable',
							'сайты',
							'style="font-size:83%; line-height:95%;"',
							'site',
						  },
	['примечание']      = { 'class=unsortable',
							'примечание',
							'style="font-size:83%; line-height:95%;"',
							'addendum',
						  },
	['#явка']           = { '',
							'явка',
							'',
							'x_turnout',
						  },
	['#досрочно']       = { '',
							'доср.',
							'',
							'x_before',
						  },
	['#дома']           = { '',
							'дома',
							'',
							'x_home',
						  },
	['#власть']         = { '',
							'власть',
							'',
							'x_loyal',
						  },
	['#явка%']          = { '',
							'явка',
							'',
							function ( row )
								if (row.quantity or 0) ~= 0 then
									return string.format('%4.1f', tonumber (row.x_turnout) / tonumber (row.quantity) * 100) .. '%'
								else
									return ''
								end
							end,
						  },
	['#досрочно%']      = { '',
							'доср.',
							'',
							function ( row )
								if (row.quantity or 0) ~= 0 then
									return string.format('%4.1f', tonumber (row.x_before) / tonumber (row.quantity) * 100) .. '%'
								else
									return ''
								end
							end,
						  },
	['#дома%']          = { '',
							'дома',
							'',
							function ( row )
								if (row.quantity or 0) ~= 0 then
									return string.format('%4.1f', tonumber (row.x_home) / tonumber (row.quantity) * 100) .. '%'
								else
									return ''
								end
							end,
						  },
	['#власть%']        = { '',
							'власть',
							'',
							function ( row )
								if (row.quantity or 0) ~= 0 then
									return string.format('%4.1f', tonumber (row.x_loyal) / tonumber (row.quantity) * 100) .. '%'
								else
									return ''
								end
							end,
						  },
	['skip']            = { '',
							'нипанятна',
							'',
							'',
						  },
}


local function printTableRow( list, row )
	outRes[#outRes+1] = '<tr>'
	for _, col in ipairs( list ) do
		local z = tabC[col]

		outRes[#outRes+1] = '<td ' .. z[3] .. '>'
		if type( z[4] ) == 'function' then
			outRes[#outRes+1] = z[4](row) .. '</td>'
		else
			outRes[#outRes+1] = mw.text.decode( row[z[4]] or '' ) .. '</td>'
		end
	end
	outRes[#outRes+1] = '</tr>\n'
end

local function printTableHeader( list )
	outRes[#outRes+1] = '<tr>'
	for t, col in ipairs( list ) do
		local z = tabC[col]
		if z then
			z = '<th ' .. z[1] .. '>' .. z[2] .. '</th>'
		else
			z = '<th style="background-color:red">' .. col .. '</th>'
			list[t] = 'skip'
		end
		outRes[#outRes+1] = z
	end
	outRes[#outRes+1] = '</tr>'
end

--------------------------------------------------------------------------- maps
local icoName = {
	['ИК']          = 'ik';
	['ТИК']         = 'tik';
	['ИКМО']        = 'ikmo';
	['полиция']     = 'ovd';
	['ОВД']         = 'ovd';
	['МВД']         = 'ovd';
	['прокуратура'] = 'proc';
	['СК']          = 'ck';
	['МРСО']        = 'ck';
	['суд']         = 'sud2';
	['адм']         = 'adm2';
}


local function bQ (s)
	return mw.ustring.gsub (s, '"', '\\"')
end


local function pointName( row )
	if (row.number or '') ~= '' then
		return 'УИК ' .. bQ( row.number )
	else
		return bQ( row.name )
	end
end
local function pointAddr( row )
	if ( row.room_org or '' ) == '' then
		return bQ( row.room_addr )
	else
		return bQ( row.room_addr .. ' — ' .. row.room_org )
	end
end
local function pointIcon( row )
	if (row.number or '') ~= '' then
		local numb = mw.ustring.sub ('0000' .. normUIK (row.number),-4)
		return 'http://www.nablawiki.ru/images/NablaIcons/E/' .. string.sub (numb,1,1) .. '/uik' .. numb .. '-e.png'
	else
		local icon =icoName[row.label]
		if icon then
			return 'http://www.nablawiki.ru/images/NablaIcons/E/gov/' .. icon .. '-e.png'
		else
			return ''
		end
	end
end

local function queryObj( name )
--	outputInfo('<br>get ' .. name .. '==' .. mw.title.getCurrentTitle().fullText)
	if mw.title.getCurrentTitle().fullText == name then
--		outputInfo('сами себя')
		return nil
	end
	return cargo.query( 'Objects',
			'_pageName, number, mun, gas, district, koib, temp, quantity, members, room_addr, room_org, '
				.. 'room_geo__lat, room_geo__lon, room_geo_s, room_geo_p,room_phone, '
				.. 'office_addr, office_org, office_geo__lat, office_geo__lon, office_geo_p, office_geo_s, '
				.. 'office_phone, border, addendum, x_turnout, x_before, x_home, x_loyal, '
				.. 'name, site, label',
			{
				where = '_pageName="' .. name .. '"',
				orderBy = 'number',
				limit = 1000,
			}
		)
end


local function kml( collection )
	local result = { [[
<?xml version="1.0" encoding="utf-8"?>
 <kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>]]
		}
	for _, finam in ipairs( collection ) do
		local cQuery = queryObj( finam )
		if #cQuery ~= 0 then
			result[#result+1] = [[
    <Folder>
      <name>]] .. finam .. [[</name>
      <description>Информация для выборов</description>]]
			for _,row in ipairs( cQuery ) do
				result[#result+1] = '      <Placemark>'
				result[#result+1] = '        <name>' .. pointName( row ) .. '</name>'
				result[#result+1] = '        <description>' .. pointAddr( row ) .. '</description>'
				result[#result+1] = [[
        <Point>
          <coordinates>]] .. row.room_geo__lon .. ',' .. row.room_geo__lat .. [[</coordinates>
        </Point>
        <Style>
          <IconStyle>
            <Icon>
              <href>]] .. pointIcon( row ) .. [[</href>
            </Icon>
            <hotSpot x="11" y="0" xunits="pixels" yunits="pixels" />
          </IconStyle>
        </Style>
      </Placemark>]]
			end
			result[#result+1] = '    </Folder>'
		end
	end
	result[#result+1] = [[
  </Document>
 </kml>]]
	return table.concat( result, '\n' )
end


local function gpx( collection )
	local result = { [[
<?xml version="1.0" encoding="UTF-8"?>
<gpx
  version="1.0"
  creator="http://www.nablawiki.ru"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.topografix.com/GPX/1/0"
  xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">]]
		}
	for _, finam in ipairs( collection ) do
		local cQuery = queryObj( finam )
		if #cQuery ~= 0 then
			for _,row in ipairs( cQuery ) do
				result[#result+1] = '  <wpt lat="' .. row.room_geo__lat .. '" lon="' .. row.room_geo__lon .. '">'
				result[#result+1] = '    <name>' .. pointName( row ) .. '</name>'
				result[#result+1] = '    <desc>' .. pointAddr( row ) .. '</desc>'
--				result[#result+1] = '    <cmt>' .. pointAddr( row ) .. '</cmt>'
				result[#result+1] = '  </wpt>'
			end
		end
	end
	result[#result+1] = [[
</gpx>]]
	return table.concat( result, '\n' )
end

local function json( collection )
	local result = { [[
{
  "type": "FeatureCollection",
  "features": []]
		}
	for _, finam in ipairs( collection ) do
		local cQuery = queryObj( finam )
		if #cQuery ~= 0 then
			for _,row in ipairs( cQuery ) do
				local outside = ''
				if tonumber( row.room_geo_s ) == 1 then
					outside = ', "outside": 1'
				end
				result[#result+1] = [[
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": []] .. row.room_geo__lon .. ',' .. row.room_geo__lat .. [[]
      },
      "properties": {]]
				-- show --
				result[#result+1] = '        "name": "' .. pointName( row ) .. '",'
				result[#result+1] = '        "description": "' .. pointAddr( row ) .. '",'
				result[#result+1] = '        "icon": {'
				result[#result+1] = '          "iconUrl": "' .. pointIcon( row ) .. '",'
				result[#result+1] = [[
          "iconSize": [39, 30],
          "iconAnchor": [11, 30],
          "popupAnchor": [9, -30]
        }]] .. outside .. [[
      }
    },]]
			end
		end
	end
	result[#result+1] = [[
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [37.672181,55.772679]
      },
      "properties": {
        "name": "Колл-центр",
        "description": "За честные выборы!",
        "icon": {
          "iconUrl": "http://www.nablawiki.ru/images/NablaIcons/E/9/uik9999-e.png",
          "iconSize": [39, 30],
          "iconAnchor": [11, 30],
          "popupAnchor": [9, -30]
        },
        "outside": 1
      }
    }
  ]
}]]
	return table.concat( result, '\n' )
end


--[===[
------------------------------------------------------------------------- Сборка
p['Сборка'] = function ( frame )
	global.set( 'ObjCollection', frame:getParent().args[1] )
	return nil
end

------------------------------------------------------------- Обработка объектов
p[''] = function ( frame )
	local pa, subp = mw.title.getCurrentTitle()
	pa, subp = pa.baseText, pa.subpageText
	local collection, base = makeCollection()
--	outputInfo( mw.dumpObject( collection ) )
	local kmlpath = frame:callParserFunction( '#t2f', base .. '.kml', kml( collection ) )
	local gpxpath = frame:callParserFunction( '#t2f', base .. '.gpx', gpx( collection ) )
	local jsonpath = frame:callParserFunction( '#t2f', base .. '.json', json( collection ) )
--	outRes[#outRes+1] = '(((' .. (base or '!!!') .. ')))' ..  kml( collection) .. '\n\n\n\n'
	local csvpath = frame:callParserFunction( '#t2f', pa .. ' - ' .. subp .. '.csv', csv() )


	outRes[#outRes+1] = '<table class="wikitable sortable"><caption style="font-size:80%; line-height:110%; text-align:right;">'
		.. 'Скачать [' .. csvpath .. ' файл в формате csv]'
		.. '([[Как работать с таблицей объектов в формате csv|?]])</caption>'
	local list = mw.text.trim( mw.ustring.lower( frame:getParent().args[1] or '' ) )
	if list == '' then
		if subp == 'УИК' then
			list = uikList
--		elseif subp == 'Власти' or subp == 'Сервисы' then
		else
			list = othList
		end
	else
		list = mw.text.split( list, '[\t\r\n\f ]*,[\t\r\n\f ]*' )
	end
	printTableHeader( list )
	for _, row in ipairs( ObjTab ) do
		printTableRow( list, row )
	end
	outRes[#outRes+1] = '</table>'
	return makeOut( frame )
end
--]===]
-------------------------------------------------------------------------- Table
local function editnotice( ctg )
	return 'Избирательные_кампании/Объекты/' .. ctg .. '/editnotice'
end
local function pattern( ctg )
	return 'Избирательные_кампании/Объекты/' .. ctg .. '/pattern'
end

----------------------------------------------------------------- |checkCollision|
local function checkCollision( filename )
	local cq = cargo.query( 'file_cmd','_pageName', { where = 'filename="' .. filename .. '"' } )
	return 
		#cq == 0 or ( #cq == 1 and cq[1]._pageName == mw.title.getCurrentTitle().text ), 
		#cq == 0 or ( 'Файл «' .. filename .. '» уже определен на странице «' .. cq[1]._pageName .. '»' )
end


----------------------------------------- |Table (Шаблон:Печать таблицы объектов)|
-- Используется на страницах кампаний и на страницах определения объектов.
-- При использовании на страницах определения объектов (выясняется по наличию
-- global.ObjectTab) формирует csv.
-- На страницах кампаний категория определяется параметром _категория_ (по умолчанию
-- «УИК»). В таблицу включается соответствующая подстраница данной страницы, либо,
-- при задании набора предшествующим шаблоном Объединение (выясняется по наличию
-- global.PagesCollection), подстраницы указанных страниц.

function p.Table( frame )
	local args = frame:getParent().args
	local objTable = global.get( 'ObjTab' )
	local curPage = mw.title.getCurrentTitle()
	local ctg
	local collection, abbrs
	local caption
	if objTable then
		local pa, subp = curPage.baseText, curPage.subpageText
		if pa == subp or not categories[subp] then
			error( 'Определения объектов могут задаваться лишь на подстраницах с фиксированными именами (УИК, Власти, Сервисы)' )
		end
		ctg = subp
		collection = { pa }
		local csvname = string.gsub( curPage.text, '[:/]', { [':'] = '^', ['/'] = '_' } ) .. '.csv'
		local csvpath = mw.site.server .. mw.site.scriptPath .. '/disk/' .. mw.uri.encode( csvname, 'PATH' )
		assert( checkCollision( csvname ) )
		caption = 'Скачать [' .. csvpath .. ' файл в формате csv]'
			.. ' ([[Как работать с таблицей объектов в формате csv|?]])'
			.. frame:expandTemplate{
				title = 'Таблица файлов',
				args = {
					filename = csvname,
					pagetitle = curPage.text,
					command = '{\\{ Disk.csv }}',
				} }
	elseif categories[curPage.subpageText] then
		error( 'Не заданы объекты' )
	else
		collection = global.get( 'PagesCollection' ) or { curPage.text }
		abbrs = global.get( 'PagesAbbrCollection' ) or { 'здесь' }
		ctg = args['категория'] or 'УИК'
		caption = {}
		for i, page in ipairs( collection ) do
			table.insert( caption, '[{{fullurl:' .. page .. '/' .. ctg
				.. '|action=edit&preload=' .. pattern( ctg ) .. '}} '
				.. abbrs[i] .. ']' )
		end
		caption = 'Редактировать сведения: ' .. table.concat( caption, ' &bull; ' )
	end
	table.insert( outRes, '<table class="wikitable sortable"><caption style="font-size:80%; line-height:110%; text-align:right;">' )
	table.insert( outRes, caption )
	table.insert( outRes, '</caption>' )

	local list = mw.text.trim( mw.ustring.lower( args[1] or '' ) )
	if list == '' then
		if ctg == 'УИК' then
			list = uikList
		else
			list = othList
		end
	else
		list = mw.text.split( list, '[\t\r\n\f ]*,[\t\r\n\f ]*' )
	end

	printTableHeader( list )
	for _, finam in ipairs( collection ) do
		local cQuery = cargo.query( 'ObjectsPage', '_pageName,geoparam,region',
			{ where = '_pageName="' .. finam .. '/' .. ctg .. '"' } )
		if #cQuery ~= 0 then
			cQuery = cQuery[1]
			GeoParam = cQuery.geoparam
			Region = cQuery.region

			cQuery = queryObj( finam .. '/' .. ctg ) or objTable
			for _,row in ipairs( cQuery ) do
				printTableRow( list, row )
			end
		elseif abbrs[1] == 'здесь' then -- только для простых собственных детей
			if mw.title.new( finam .. '/' .. ctg ).id == 0 then
				outputInfo( frame:preprocess(
					'{{Доделать|доделать|header=Страница «'
					.. finam .. '/' .. ctg .. '» еще не создана.'
					.. '|Чтобы создать страницу и увидеть инструкцию по ее наполнению, нажмите ['
					.. '{{fullurl:' .. finam .. '/' .. ctg
					.. '|action=edit&preload=' .. pattern( ctg )
					.. '}} сюда]}}' ) )
			else
				outputInfo( frame:preprocess(
					'{{Доделать|доделать|header=Страница «'
					.. finam .. '/' .. ctg .. '» не содержит определений объектов.'
					.. ' Возможно, она имеет устаревший формат.'
					.. '|Чтобы заполнить страницу в правильном формате, нажмите ['
					.. '{{fullurl:' .. finam .. '/' .. ctg
					.. '|action=edit&preload=' .. pattern( ctg )
					.. '}} сюда]}}' ) )
			end
		end
	end
	table.insert( outRes, '</table>' )
	return makeOut( frame )
end


-------------------------------------------- |MakeMaps (Шаблон:Формирование карт)|
-- Эта функция реализует шаблон «Формирование карт».
-- Она вызывается на страницах кампаний.
-- На карте размещаются объекты с подстраниц данной страницы, либо,
-- при задании набора предшествующим шаблоном Объединение (выясняется по наличию
-- global.PagesCollection), с подстраниц указанных страниц.

function p.MakeMaps( frame )
	local curPage = mw.title.getCurrentTitle().text
	local collection = global.get( 'PagesCollection' ) or { curPage }
	local allPages = table.concat( collection, '|' )
	local baseName = string.gsub( curPage, '[:/]', { [':'] = '^', ['/'] = '_' } )
	assert( checkCollision( baseName .. '.kml' ) )
	assert( checkCollision( baseName .. '.gpx' ) )
	assert( checkCollision( baseName .. '.json' ) )
	local res = ''
	for _, geotype in ipairs( { '.kml', '.gpx', '.json' } ) do
		assert( checkCollision( baseName .. geotype ) )
		res = res .. frame:expandTemplate{
			title = 'Таблица файлов',
			args = {
				filename = baseName .. geotype,
				pagetitle = curPage,
				command = '{\\{ Disk' .. geotype.. ' |' .. allPages .. '}}',
			} }
		for _, page in ipairs( collection ) do
			for ctg, _ in pairs( categories ) do
				res = res .. frame:expandTemplate{
								title = 'Таблица влияющих страниц',
								args = {
									filename = baseName .. geotype,
									page = page .. '/' .. ctg ,
								} }
			end
		end
	end
	return res .. '<p><i class="fa fa-globe"></i> &nbsp;Объекты на [http://'
		.. mw.uri.new(mw.site.server).host .. mw.site.scriptPath .. '/tools/map/index.php?json='
		.. mw.uri.encode( baseName, 'QUERY' ) .. ' географической карте].'
		.. ' Для использования во внешних приложениях доступны данные об объектах в форматах '
		.. ' [' .. mw.site.server .. mw.site.scriptPath .. '/disk/' .. mw.uri.encode( baseName, 'QUERY' ) .. '.gpx gpx],'
		.. ' [' .. mw.site.server .. mw.site.scriptPath .. '/disk/' .. mw.uri.encode( baseName, 'QUERY' ) .. '.kml kml] и'
		.. ' [' .. mw.site.server .. mw.site.scriptPath .. '/disk/' .. mw.uri.encode( baseName, 'QUERY' ) .. '.json geojson]'
		.. ' ([[Как использовать файлы с географической информацией|?]])</p>'
end


------------------------------------------------------ |DiskCSV (Шаблон:Disk.csv)|
function p.DiskCSV( frame )
	local name = mw.title.getCurrentTitle().text
	local ObjTab = cargo.query( 'Objects',
			'number, mun, gas, district, koib, temp, quantity, members, room_addr, room_org, '
				.. 'room_geo__lat, room_geo__lon, room_geo_s, room_geo_p,room_phone, '
				.. 'office_addr, office_org, office_geo__lat, office_geo__lon, office_geo_p, office_geo_s, '
				.. 'office_phone, border, addendum, x_turnout, x_before, x_home, x_loyal, '
				.. 'name, site, label',
			{
				where = '_pageName="' .. name .. '"',
				limit = 1000,
			}
		)
	return frame:preprocess( '__NOGLOSSARY__<pre>' .. csv(ObjTab) .. '</pre>' )
end


------------------------------------------------------ |DiskKML (Шаблон:Disk.kml)|
function p.DiskKML( frame )
	local args = frame:getParent().args
	local collection = {}
	for _, u in ipairs( args ) do
		table.insert( collection, u .. '/УИК' )
		table.insert( collection, u .. '/Власти' )
		table.insert( collection, u .. '/Сервисы' )
	end
	return frame:preprocess( '__NOGLOSSARY__<pre>' .. kml(collection) .. '</pre>' )
end


------------------------------------------------------ |DiskGPX (Шаблон:Disk.gpx)|
function p.DiskGPX( frame )
	local args = frame:getParent().args
	local collection = {}
	for _, u in ipairs( args ) do
		table.insert( collection, u .. '/УИК' )
		table.insert( collection, u .. '/Власти' )
		table.insert( collection, u .. '/Сервисы' )
	end
	return frame:preprocess( '__NOGLOSSARY__<pre>' .. gpx(collection) .. '</pre>' )
end


---------------------------------------------------- |DiskJSON (Шаблон:Disk.json)|
function p.DiskJSON( frame )
	local args = frame:getParent().args
	local collection = {}
	for _, u in ipairs( args ) do
		table.insert( collection, u .. '/УИК' )
		table.insert( collection, u .. '/Власти' )
		table.insert( collection, u .. '/Сервисы' )
	end
	return frame:preprocess( '__NOGLOSSARY__<pre>' .. json(collection) .. '</pre>' )
end


------------------------------------------------------------------- |Contraparser|
-- Вспомогательная функция для Шаблон:Проверка актуальности файла
function p.Contraparser( frame )
	local name = frame.args[1]
	local cq = cargo.query( 'file_cmd', 'pagetitle,command', { where = 'filename="' .. name .. '"' } )
	return '||' .. cq[1].pagetitle .. '||' .. cq[1].command
end

return p