Модуль:Statistical (Bk;rl,&Statistical)

Перейти к навигации Перейти к поиску
Документация

Реализация шаблона {{Население}} и, вероятно, некоторых других шаблонов.

local p = {}

local templateIncrease = 'Увеличение'
local templateDecrease = 'Уменьшение'
local templateNoChange = 'Без изменения'
 
local Regions = mw.loadData("Модуль:Statistical/Regions")
bit32 = require( 'bit32' )

	local function LimitDouble(Val)
			local MaxNumber = 2147483648
			return Val - (math.floor(Val / MaxNumber) * MaxNumber)
	end
	
	local function shl(Val, Shift)
			if Shift > 0 then
					return  LimitDouble(Val * (2 ^ Shift))
			else
					return Value
			end 
	end

	local function shr(Val, Shift)
			if Shift > 0 then 
				return math.floor(Val / (2 ^ Shift))
			else
				return Val
			end
	end
	-- Код основан на алгоритме SuperFastHash:
		-- http://www.azillionmonkeys.com/qed/hash.html
	local function MakeHash(PlaceName)    
		local dataLength = mw.ustring.len(PlaceName)
			if dataLength == 0 then return 0 end
		local hash = dataLength
		local remainingBytes =  math.fmod(dataLength, 2)
			local numberOfLoops = math.floor(dataLength / 2)
			local currentIndex = 0
			local tmp = 0
			while (numberOfLoops > 0) do 
			hash = LimitDouble(hash + mw.ustring.codepoint(PlaceName, currentIndex + 1))
			tmp = bit32.bxor(shl(mw.ustring.codepoint(PlaceName, currentIndex + 2), 11), hash)
			hash = bit32.bxor(shl(hash, 16), tmp)
			hash = LimitDouble(hash + shr(hash, 11))
			currentIndex = currentIndex + 2
			numberOfLoops = numberOfLoops - 1
			end
		if remainingBytes == 1 then
			hash = LimitDouble(hash + mw.ustring.codepoint(PlaceName, currentIndex + 1))
			hash = bit32.bxor(hash, shl(hash, 10))
			hash = LimitDouble(hash + shr(hash, 1))
			end
		hash = bit32.bxor(hash, shl(hash, 3))
		hash = LimitDouble(hash + shr(hash, 5))
		hash = bit32.bxor(hash, shl(hash, 4))
		hash = LimitDouble(hash + shr(hash, 17))
		hash = bit32.bxor(hash, shl(hash, 25))
		hash = LimitDouble(hash + shr(hash, 6))
		return hash
	end
 
local function First_less_Second(a, b)  
		local LenA = mw.ustring.len(a)
		local LenB = mw.ustring.len(b)
		for i = 1, (LenA < LenB) and LenA or LenB do
				if mw.ustring.codepoint(a, i, i) ~= mw.ustring.codepoint(b, i, i) then
						return mw.ustring.codepoint(a, i, i) < mw.ustring.codepoint(b, i, i) 
				end
		end
		return LenA < LenB
end
 
function GetHashData(PlaceHash)     
	local NumPage = math.floor((PlaceHash - 1) / 33554432 + 2)
	if NumPage == 2 and (PlaceHash - 1) < 16777216 then NumPage = 1 end
	local templatename
	if NumPage < 10  then templatename = "Население/STA-00"..NumPage else templatename = "Население/STA-0"..NumPage	end
	local page = mw.title.new(templatename, 10)
	local RawData = page:getContent()
	HashData = RawData:match("|" .. PlaceHash .. "=([^\n<]+)")
	if (HashData=="") then HashData=nil 
	elseif (tonumber(HashData)~=nil) then HashData=tonumber(HashData) end
	return HashData
end

function p.GenerateAndReturnHash(frame)
	local args = frame:getParent().args

		if args == nil then return "Введите название объекта АТД" end
		local PlaceName = args[1]
 
		if PlaceName == nil then return "Введите название объекта АТД" end
		PlaceName = mw.text.trim(PlaceName)
		local PlaceHash = MakeHash(PlaceName)
	local NumPage = math.floor((PlaceHash - 1) / 33554432 + 2)
	if NumPage == 2 and (PlaceHash - 1) < 16777216 then NumPage = 1 end
	local templatename
	if NumPage < 10  then templatename = "Население/STA-00"..NumPage else templatename = "Население/STA-0"..NumPage	end    
		return PlaceHash..' → '..'[[Шаблон:'..templatename..']]'
end

function p.GetStat(frame)
		local args
	if frame == mw.getCurrentFrame() then
		args = frame:getParent().args
	else
		args = frame
	end
	
		if args == nil then return "Введите название объекта АТД" end
		local PlaceName = args[1]
	
	local Format = mw.text.trim ( (args[2] or "Таблица"))

		if PlaceName == nil then return "Введите название объекта АТД" end
		PlaceName = mw.text.trim(PlaceName)
	if Format == 'Хеш' or Format == 'х'  then do return MakeHash(PlaceName) end end
		
		local Region = args['Регион'] or ''

		local check = args['check'] or ''
		if check == '' or check == '0' or check == 'false' then
			check = false;
		else
			check = true;
		end

		local PlaceData = nil
	local RegionData = nil

		local CheckRedirect = false
		local NeedhandleRedirect = false
		
	local function GetPlaceData()
		local PlaceHash = MakeHash(PlaceName)
			PlaceData = nil
		RegionData = nil
		local test = PlaceName:match("%((%P+)%)")
		if Region=='' and test~='' then
			-- Если в имени населенного пункта есть уточнение с именем региона, то можно не пользоваться индексной таблицей, то есть не вызывать GetHashData
			local value = Regions[test]
			if(value) then
					RegionData = mw.loadData("Модуль:Statistical/"..value)
					PlaceData = RegionData[PlaceHash]
			end
		elseif Region ~= "" then
			-- магия, позволяющая указывать не только индекс, но и значение напрямую
			Region = Regions[Region] or Region
			RegionData = mw.loadData("Модуль:Statistical/"..Region)
			PlaceData = RegionData[PlaceHash]
		end
	
		if PlaceData == nil then
			local RegionPage = GetHashData(PlaceHash)
			if RegionPage == 0 then return "#Н/Д"..frame:callParserFunction{name = '#tag:ref', args = {PlaceName .." > Совпадение хешей. Пожалуйста, укажите регион вручную, например <code><nowiki>{{ Население | " .. PlaceName .. " | " .. Format .." | Регион = Приморский край }}</nowiki></code>" .. ((args.nocat and "") or "[[Категория:Википедия:Статьи с неправильными параметрами шаблона Население]]")}} end 
			if RegionPage ~= nil then
				if type(tonumber(RegionPage)) == "number" then
					-- очень плохая вещь, ибо GetHashData очень дорогая по памяти функция
					PlaceHash=tonumber(RegionPage)
					RegionPage = GetHashData(PlaceHash) 
				end
				if type(RegionPage) == "table" then RegionPage = RegionPage[PlaceName] end
				if RegionPage ~= nil then 
					RegionData = mw.loadData("Модуль:Statistical/"..RegionPage) 
					if RegionData ~= nil then PlaceData = RegionData[PlaceHash] end
				end
			end
		end 
	end

	GetPlaceData()
		-- Проверка на редирект, если данные не найдены
	if PlaceData == nil then
		local page = mw.title.makeTitle('', PlaceName)
		if page.redirectTarget then 
			PlaceName = page.redirectTarget.text 
			GetPlaceData()
		end
	end

	local IsWikidata 
	-- Если данные перемещены из шаблона Население в Wikidata, то работаем с ними
	if PlaceData ~= nil then 
		IsWikidata = PlaceData == "Wikidata" 
	else
			if check then
				return 0;
			else
				--return "#Н/Д"..frame:callParserFunction{name = '#tag:ref', args = {PlaceName .." > Данные не обнаружены. Возможно, страница переименовывалась. Проверьте справочник"..  ((args.nocat and "") or "[[Категория:Википедия:Статьи с неправильными параметрами шаблона Население]]")}} 
				-- Вместо прекращения работы при неудаче поиска данных в шаблоне Население попытка поиска в Wikdata
				IsWikidata = true
			end
	end
	if check then
		return 1;
	end
	
		local LastRecord = 0
		local Qcode 
		
	if IsWikidata then
		Qcode = args.from or mw.wikibase.getEntityIdForTitle(PlaceName) or ""
		if Qcode == "" then
			local page = mw.title.makeTitle('', PlaceName)
			if page.redirectTarget then 
				PlaceName = page.redirectTarget.text 
				Qcode = args.from or mw.wikibase.getEntityIdForTitle(PlaceName) or ""
			end
		end
		if Qcode == "" then
			return "#Н/Д"..frame:callParserFunction{name = '#tag:ref', args = {PlaceName .." > Данные в Wikidata не обнаружены."..  ((args.nocat and "") or "[[Категория:Википедия:Статьи с неправильными параметрами шаблона Население]]")}} 
		end
		if next(mw.wikibase.getBestStatements(Qcode, 'P1082')) == nil then
			return "#Н/Д"..frame:callParserFunction{name = '#tag:ref', args = {PlaceName .." > Данные в Wikidata не обнаружены."..  ((args.nocat and "") or "[[Категория:Википедия:Статьи с неправильными параметрами шаблона Население]]")}} 
		end
	else
			for k in pairs(PlaceData) do
				LastRecord = LastRecord + 1
				if args[3] ~= nil and PlaceData[LastRecord][1] == tonumber(args[3]) then
				break
				end
			end
	end
		local NumRecord = LastRecord
 
	local function FormatY()    
			if IsWikidata then
			return frame:expandTemplate{ title = "Wikidata", args={from=Qcode,['property-module']="Statistical/Wikidata",['property-function']="FormatY",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
				return PlaceData[NumRecord][1]
			end
	end
 
	local function FormatN()    
			if IsWikidata then
			return frame:expandTemplate{ title = "Wikidata", args={from=Qcode,['property-module']="Statistical/Wikidata",['property-function']="FormatN",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
				return PlaceData[NumRecord][2]
			end
	end
 
	local function FormatS(SourceType)  
			if IsWikidata then
			return frame:expandTemplate{ title = "Wikidata", args={from=Qcode,['property-module']="Statistical/Wikidata",['property-function']="FormatS",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
				if ( PlaceData[NumRecord][3] == nil or PlaceData[NumRecord][3] == '' ) then
						return ""
				else
						local Source1
						local Source2 
						if string.find(PlaceData[NumRecord][3],"%d+[A-Z]+")==1 then
							if RegionData['Источники'][PlaceData[NumRecord][3]] ~= nil then
								Source1 = RegionData['Источники'][PlaceData[NumRecord][3]][1]
									Source2 = PlaceData[NumRecord][3]
							else
								Source1 = PlaceData[NumRecord][3] .. '[[Категория:Википедия:Статьи с неправильными источниками в модуле Statistical]]'
									Source2 = ""
							end
						else
								Source1 = PlaceData[NumRecord][3]
								Source2 = ""
						end
						if string.find(Source1, "https?://")==1 then
								Source1 = '['..Source1..']'
						end
				if SourceType == "и" then
					return Source1
				end
						if Source2 == "" then
								return frame:callParserFunction{name = '#tag:ref', args = {Source1}}
						else
								return frame:callParserFunction{name = '#tag:ref', args = {Source1, name = Source2}}
						end
				end
			end
	end        
 
	local function FormatF()
			if IsWikidata then
			return frame:expandTemplate{ title = "Wikidata", args={from=Qcode,['property-module']="Statistical/Wikidata",['property-function']="FormatF",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
			local lang = mw.language.getContentLanguage()
			return lang:formatNum( PlaceData[NumRecord][2] )
			end
	end
 
	local function FormatT()    
			if IsWikidata then
			return frame:expandTemplate{ title = "Wikidata", args={from=Qcode,['property-module']="Statistical/Wikidata",['property-function']="FormatT",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
				if NumRecord > 1 then
						if PlaceData[NumRecord][2] > PlaceData[NumRecord - 1][2] then
								return frame:expandTemplate{
									title = templateIncrease
								}
						elseif PlaceData[NumRecord][2] < PlaceData[NumRecord - 1][2] then
								return frame:expandTemplate{
									title = templateDecrease
								}
						else
								return frame:expandTemplate{
									title = templateNoChange
								}
						end
				else
						return ""
				end            
			end
	end

	local function FormatD()    
			if IsWikidata then
			return frame:expandTemplate{ title = "Wikidata", args={from=Qcode,['property-module']="Statistical/Wikidata",['property-function']="FormatD",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
			local tempHeight = 320
			local tempWidth = 800
			mw.logObject(LastRecord)
			local tickCount = 5
			if LastRecord > 100 then
				tickCount = 10
			end
			local tempMod = math.fmod (LastRecord, tickCount)
			if LastRecord < 40 then 
				tempHeight = 200  + 170 * (LastRecord - 1) / 40
				tempWidth = 200 + 600 * (LastRecord - 1) / 40
			end
			local tempGroup = ""
			local tempTooltip = ""
			local tempLegend = ""
			for k in pairs(PlaceData) do 
				NumRecord = k
				tempGroup = tempGroup .. FormatN() .. ":"
				tempTooltip = tempTooltip .. FormatF() .. " (" .. FormatY() .. "):"
				if LastRecord < tickCount or math.fmod (k, tickCount) == tempMod  then tempLegend = tempLegend .. FormatY() end
				tempLegend = tempLegend .. ":"
			end
			tempGroup = string.sub (tempGroup, 1, string.len (tempGroup)-1)
			tempTooltip = string.sub (tempTooltip, 1, string.len (tempTooltip)-1)
			tempLegend = string.sub (tempLegend, 1, string.len (tempLegend)-1)
				local barChart = require('Модуль:Chart')['bar chart'];
			local Diagram = {
				['height'] = tempHeight,
				['width'] = tempWidth,
				['group 1'] = tempGroup,
				['tooltip 1'] = tempTooltip,
				['colors'] = "#B0C4DE",
				['x legends'] = tempLegend,
				['group names'] = 'Численность населения',
				['default color'] = '#1E90FF'
				}    	
			local cframe = mw.getCurrentFrame();
			return barChart(cframe:newChild{ title=cframe.title, args = Diagram})     
			end
	end

	local function FormatG()    
		local csv = ""
			if IsWikidata then
			csv = frame:expandTemplate{ title = "Wikidata", args={from=Qcode,['property-module']="Statistical/Wikidata",['property-function']="FormatG",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
			csv = "year,population,formatted\\n"
			for k in pairs(PlaceData) do 
				NumRecord = k
				csv = csv .. FormatY() .. "," .. FormatN() .. "," .. FormatF() .. "\\n"
			end
		end
		local json = [[{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 400,
"height": 200,

"data": [
	{
			"name": "table",
			"values": "]] .. csv .. [[",
			"format": {
				"parse": {"year": "integer", "population": "integer", "formatted": "string"},
				"type": "csv"
			},
			// Convert year integer (2016) into a date object (2016-01-01)
			"transform": [{ "type": "formula", "as": "date", "expr": "utc(datum.year,0,1)" }]
		}
	],

"scales": [
		// The dates are scaled to the "x" axis - the width of the graph
		{
			"name": "x",
			"type": "utc",
			"range": "width",
			"domain": {"data": "table", "field": "date"}
		},
		// The population are scaled to the "y" axis - the height of the graph
		{
			"name": "y",
			"type": "linear",
			"range": "height",
			"domain": {"data": "table", "field": "population"}
		}
	],

	// Simple axis with horizontal grid lines
	"axes": [
		{"scale": "x", "orient": "bottom", "tickCount": 5},
		{"scale": "y", "orient": "right", "tickCount": 5, "grid": true, "format": "d"}
	],

// The graph is drawn with two elements a thick line at the top, and a semi-transparent area below
"marks": [
		{
			"type": "area",
			"from": {"data": "table"},
			"encode": {
				"enter": {
					"x": {"scale": "x", "field": "date"},
					"y": {"scale": "y", "value": 0},
					"y2": {"scale": "y", "field": "population"},
					"fill": {"value": "#99B2CC"},
					"fillOpacity": {"value": 0.35},
					"interpolate": {"value": "linear"}
				}
			}
		},
		{
			"type": "line",
			"from": {"data": "table"},
			"encode": {
				"enter": {
					"x": {"scale": "x", "field": "date"},
					"y": {"scale": "y", "field": "population"},
					"stroke": {"value": "#99B2CC"},
					"strokeWidth": {"value": 3},
					"interpolate": {"value": "linear"}
				}
			}
		},
		{
			"type": "symbol",
			"from": {"data": "table"},
			"encode": {
				"enter": {
					"x": {"scale": "x", "field": "date"},
					"y": {"scale": "y", "field": "population"},
					"stroke": {"value": "#99B2CC"},
					"fill": {"value": "#fff"},
					"size": {"value": 10}
				}
			}
		},
		{
			"type": "text",
			"from": {"data": "table"},
			"encode": {
				"enter": {
					"x": {"scale": "x", "field": "date"},
					"y": {"scale": "y", "field": "population", "offset": -1},
					"align": {"value": "center"},
					"opacity": {"value": "0"},
					"fill": {"value": "#000000"},
					"fontWeight": {"value": "bold"},
					"size": {"value": 4},
					"text": {"signal": "datum.formatted + ' (' + datum.year + ')'"}
				},
				"hover": {"opacity": {"value": "1"}},
				"update": {"opacity": {"value": "0"}}
			}
		}
	]
}]]
		local cframe = mw.getCurrentFrame()
		return cframe:callParserFunction{ name = '#tag:graph', args = { json, mode = 'interactive' } }
	end

	local function FormatTable()    
				-- Формирование HTML-таблицы
			if IsWikidata then
			return frame:expandTemplate{ title = "Wikidata", args={from=Qcode, column = args['Столбцов'], decor = args['Оформление'], titul = args['Заголовок'], ['property-module']="Statistical/Wikidata",['property-function']="FormatTable",property="p1082[p585][rank:preferred, rank:normal]"} }
			else
				local HTML = mw.html.create('table')
	
					local MaxData
					if args['Столбцов'] then
							Column = tonumber(args['Столбцов'])
					else
				Column = 7
					end
					if Column > LastRecord then Column = LastRecord end
	 
					if args['Оформление'] ~= nil then
							HTML:attr('class', args['Оформление'])
					else
							HTML:attr('class', 'standard') 
					end
	 
					local TempRow
					local NumRow = 0
					HTML:tag('caption')
						:wikitext(args['Заголовок'] or 'Численность населения')
					:css('background-color', 'var(--ruwiki-background-color-blue200, #cfe3ff)')
					:css('color', 'inherit')
					:css('border', '1px solid var(--border-color-base, #a2a9b1)')
					:css('border-bottom', '0')
					
					for i = 1, math.ceil(LastRecord / Column) do
							TempRow = HTML:tag('tr')
							for j = 1, Column do
					NumRecord = (i - 1) * Column + j
									if PlaceData[NumRecord] == nil then
											TempRow:tag('th'):attr('scope', 'col'):wikitext("")
									else
											TempRow:tag('th'):attr('scope', 'col'):wikitext(FormatY()..FormatS("с"))
									end
							end
	 
							TempRow = HTML:tag('tr'):css('text-align', 'center')
							for j = 1, Column do
					NumRecord = (i - 1) * Column + j
									if PlaceData[NumRecord] == nil then
											TempRow:tag('td'):wikitext("")
									else
											TempRow:tag('td'):wikitext(FormatT()..FormatF())
									end
							end
					end            
					return tostring(HTML)
		end
	end
		if Format == 'Год' or Format == 'г' then
		mw.logObject(Format)
				return FormatY()    
		elseif Format == 'Безформат' or Format == 'Число' or Format == 'ч'  then
				return FormatN()    
		elseif Format == 'Ссылка' or Format == 'с'  then
				return FormatS("с")    
		elseif Format == 'Источник' or Format == 'и'  then
				return FormatS("и")    
		elseif Format == 'Формат' or Format == 'ф'  then
				return FormatF()    
		elseif Format == 'ФорматГод' or Format == 'фг'  then
				return FormatF().." ("..FormatY()..")"    
		elseif Format == 'ФорматСсылка' or Format == 'фс'  then
				return FormatF()..FormatS()    
		elseif Format == 'ФорматСсылкаГод' or Format == 'фсг'  then
				return FormatF()..FormatS().." ("..FormatY()..")"
		elseif Format == 'Тренд' or Format == 'т'  then
				return FormatT()..FormatF()    
		elseif Format == 'Значение' or Format == 'ТрендСсылка' or Format == 'тс'  then
				return FormatT()..FormatF()..FormatS()
		elseif Format == 'ТрендСсылкаГод' or Format == 'тсг'  then
				return FormatT()..FormatF()..FormatS().." ("..FormatY()..")"
	elseif Format == 'Диаграмма' or Format == 'д' or Format == 'График' or Format == 'график'  then -- пока в Википедии отключены графики, вместо них временно выводим диаграммы
		mw.logObject(FormatD())
				return FormatD()
	elseif Format == 'График' or Format == 'график'  then
				return FormatG()
		else
			return FormatTable()
		end
		return 1, Format
end

return p