Jump to content

Module:ⵜⴰⴼⵍⵓⵖⵎⵉⵙⵜ ⴰⵎⵏⵥⴰⵡ

ⵙⴳ ⵡⵉⴽⵉⴱⵉⴷⵢⴰ
local p = {}

local function trimWhitespace(s)
	if type(s) ~= 'string' then return nil end
	s = s:match('^%s*(.-)%s*$')
	if s == '' then return nil end
	return s
end

local function getArgs(frame)
	local args = {}
	local parent = frame:getParent()
	if parent and parent.args then
		for k, v in pairs(parent.args) do
			args[k] = trimWhitespace(v)
		end
	end
	for k, v in pairs(frame.args) do
		args[k] = trimWhitespace(v)
	end
	return args
end

local function getEntity(entityId)
	if not mw.wikibase then return nil end
	if entityId == '' then entityId = nil end
	entityId = entityId or mw.wikibase.getEntityIdForCurrentPage()
	if not entityId or entityId == '' then return nil end

	local success, entity = pcall(function() return mw.wikibase.getEntity(entityId) end)
	if success and entity then
		entity.getBestStatements = function(self, propertyId)
			if not self.claims or not self.claims[propertyId] then return {} end
			local statements = {}
			for _, claim in pairs(self.claims[propertyId]) do
				if claim.mainsnak.snaktype == 'value' then
					table.insert(statements, claim)
				end
			end
			return statements
		end
		return entity
	end
	return nil
end

local function getCoordinates(entity)
	local statements = entity:getBestStatements('P625')
	if #statements > 0 then
		local val = statements[1].mainsnak.datavalue.value
		return val.latitude, val.longitude
	end
	return nil, nil
end

local function getLatestPopulation(entity, raw)
	local claims = entity:getBestStatements('P1082')
	if #claims == 0 then return nil end

	local bestClaim = nil
	local maxYear = -9999
	
	for _, claim in ipairs(claims) do
		local year = 0
		if claim.qualifiers and claim.qualifiers['P585'] then
			local timeValue = claim.qualifiers['P585'][1].datavalue.value.time
			local yearStr = string.match(timeValue, "^[%+%-](%d+)")
			if yearStr then year = tonumber(yearStr) end
		end

		if year > maxYear then
			maxYear = year
			bestClaim = claim
		elseif not bestClaim then
			bestClaim = claim
		end
	end

	if bestClaim then
		local amount = tonumber(bestClaim.mainsnak.datavalue.value.amount)
		
		if raw then return amount end

		local lang = mw.getContentLanguage()
		local formattedNum = lang:formatNum(amount)

		if maxYear > 0 then
			return formattedNum .. ' <small>(' .. maxYear .. ')</small>'
		else
			return formattedNum
		end
	end
	return nil
end

local function getLatestOutOfSchool(entity)
	local claims = entity:getBestStatements('P2573')
	if #claims == 0 then return nil end

	local bestClaim = nil
	local maxYear = -9999
	
	for _, claim in ipairs(claims) do
		local year = 0
		if claim.qualifiers and claim.qualifiers['P585'] then
			local timeValue = claim.qualifiers['P585'][1].datavalue.value.time
			local yearStr = string.match(timeValue, "^[%+%-](%d+)")
			if yearStr then year = tonumber(yearStr) end
		end

		if year > maxYear then
			maxYear = year
			bestClaim = claim
		elseif not bestClaim then
			bestClaim = claim
		end
	end

	if bestClaim then
		local amount = tonumber(bestClaim.mainsnak.datavalue.value.amount)
		local lang = mw.getContentLanguage()
		local formattedNum = lang:formatNum(amount)

		if maxYear > 0 then
			return formattedNum .. ' <small>(' .. maxYear .. ')</small>'
		else
			return formattedNum
		end
	end
	return nil
end

local function getLatestArea(entity, raw)
	local claims = entity:getBestStatements('P2046')
	if #claims == 0 then return nil end

	local bestClaim = nil
	local maxDate = -9999999999999
	
	for _, claim in ipairs(claims) do
		local currentTimestamp = -9999999999999
		
		if claim.qualifiers and claim.qualifiers['P585'] then
			local timeValue = claim.qualifiers['P585'][1].datavalue.value.time
			local yearStr = string.match(timeValue, "^[%+%-](%d+)")
			if yearStr then currentTimestamp = tonumber(yearStr) end
		end

		if currentTimestamp > maxDate then
			maxDate = currentTimestamp
			bestClaim = claim
		elseif not bestClaim then
			bestClaim = claim
		end
	end

	if bestClaim then
		local val = bestClaim.mainsnak.datavalue.value
		local amount = tonumber(val.amount)
		
		if raw then return amount end

		local lang = mw.getContentLanguage()
		return amount and lang:formatNum(amount) or nil
	end
	return nil
end

local function getLatestDensity(entity)
	local claims = entity:getBestStatements('P7959')
	if #claims > 0 then
		local val = claims[1].mainsnak.datavalue.value
		local amount = tonumber(val.amount)
		local lang = mw.getContentLanguage()
		return amount and lang:formatNum(amount) or nil
	end

	local pop = getLatestPopulation(entity, true)
	local area = getLatestArea(entity, true)

	if pop and area and area > 0 then
		local density = pop / area
		local mult = 100
		density = math.floor(density * mult + 0.5) / mult
		
		local lang = mw.getContentLanguage()
		return lang:formatNum(density)
	end

	return nil
end

local function makeRequest(entityId, property)
	local entity = getEntity(entityId)
	if not entity then return nil, false end
	
	if property == 'P1082' then
		local popValue = getLatestPopulation(entity)
		if popValue then return popValue, true end
		return nil, false
	end

	if property == 'P2573' then
		local outOfSchoolValue = getLatestOutOfSchool(entity)
		if outOfSchoolValue then return outOfSchoolValue, true end
		return nil, false
	end

	if property == 'P2046' then
		local areaValue = getLatestArea(entity)
		if areaValue then return areaValue, true end
		return nil, false
	end

	if property == 'P7959' then
		local densityValue = getLatestDensity(entity)
		if densityValue then return densityValue, true end
		return nil, false
	end

	local statements = entity:getBestStatements(property)
	if #statements > 0 then
		local valuesWithLinks = {}
		for _, statement in ipairs(statements) do
			local datavalue = statement.mainsnak.datavalue
			local valueToAdd = nil

			if datavalue.type == 'string' then
				valueToAdd = datavalue.value
			elseif datavalue.type == 'wikibase-entityid' then
				local targetEntityId = datavalue.value.id
				local label = mw.wikibase.getLabel(targetEntityId)
				
				if not label then
					local targetEntity = mw.wikibase.getEntity(targetEntityId)
					if targetEntity and targetEntity.labels then
						if targetEntity.labels.en then
							label = targetEntity.labels.en.value
						elseif targetEntity.labels.fr then
							label = targetEntity.labels.fr.value
						end
					end
				end
				
				label = label or targetEntityId
				local sitelink = mw.wikibase.getSitelink(targetEntityId)

				if sitelink then
					valueToAdd = string.format("[[%s|%s]]", sitelink, label)
				else
					valueToAdd = string.format("[[:d:%s|%s]]", targetEntityId, label)
				end

				if property == 'P17' then
					local flags = mw.wikibase.getBestStatements(targetEntityId, 'P41')
					if #flags > 0 and flags[1].mainsnak.datavalue then
						valueToAdd = '[[ⴰⴼⴰⵢⵍⵓ:' .. flags[1].mainsnak.datavalue.value .. '|20px|border|link=]] ' .. valueToAdd
					end
				end

			elseif datavalue.type == 'quantity' then
				local amount = tonumber(datavalue.value.amount)
				local lang = mw.getContentLanguage()
				valueToAdd = amount and lang:formatNum(amount) or datavalue.value.amount
			elseif datavalue.type == 'time' then
				valueToAdd = string.sub(datavalue.value.time, 9, 10) 
			end

			if valueToAdd then
				table.insert(valuesWithLinks, valueToAdd)
			end
		end
		
		if #valuesWithLinks > 0 then
			if (property == 'P421' or property == 'P17') and #valuesWithLinks > 1 then
				local container = mw.html.create('div')
				container:addClass('mw-collapsible mw-collapsed')
				container:tag('div')
					:css('font-weight', 'normal')
					:wikitext('ⵜⴰⵍⴳⴰⵎⵜ [[ⴰⴼⴰⵢⵍⵓ:Incomplete list.svg|17px|link=]]&nbsp; (' .. #valuesWithLinks .. ')') 
				local contentDiv = container:tag('div'):addClass('mw-collapsible-content')
				local ul = contentDiv:tag('ul')
				for _, v in ipairs(valuesWithLinks) do
					ul:tag('li'):wikitext(v)
				end
				return tostring(container), true
			end
			return table.concat(valuesWithLinks, ", "), true
		end
	end
	return nil, false
end

local function getValueOrWikidata(args, paramName, propertyId)
	local value = args[paramName]
	local fromWikidata = false
	if value then value = trimWhitespace(value) end
	if not value then
		if propertyId and args.qid ~= '' then
			value, fromWikidata = makeRequest(args.qid, propertyId)
		end
	end
	return value, fromWikidata
end

function p.main(frame)
	local args = getArgs(frame)
	local entityId = args.qid 
	if entityId == '' then entityId = nil end
	entityId = entityId or mw.wikibase.getEntityIdForCurrentPage()

	local entity = getEntity(entityId)

	frame:extensionTag('templatestyles', '', {src = 'ⴰⵍⴱⵓⴹ:ⵜⴰⴼⵍⵓⵖⵎⵉⵙⵜ ⴰⵎⵏⵥⴰⵡ/styles.css'})

	local infobox = mw.html.create('table')
	infobox:addClass('infobox geography vcard')
	infobox:css({
		['border'] = '1px solid #a2a9b1',
		['background-color'] = '#f8f9fa',
		['color'] = 'black',
		['margin'] = '0.5em 0 0.5em 1em',
		['float'] = 'right',
		['clear'] = 'right',
		['font-size'] = '90%',
		['width'] = '18em',
	})

	local headerValue = args['ⵉⵙⵎ']
	if not headerValue and entityId then
		headerValue = mw.wikibase.getLabel(entityId)
		if not headerValue and entity and entity.labels and entity.labels.en then
			headerValue = entity.labels.en.value
		end
	end
	headerValue = headerValue or mw.title.getCurrentTitle().text

	local headerRow = infobox:tag('tr')
	local headerCell = headerRow:tag('th')
		:attr('colspan', 2)
		:css({
			['background-color'] = '#006400',
			['color'] = '#ffffff',
			['padding'] = '0.4em',                
			['text-align'] = 'center',
			['font-weight'] = 'bold',
			['font-size'] = '125%',
			['position'] = 'relative'
		})

	local headerContainer = mw.html.create('div')
		:css({
			['padding-right'] = '50px', 
			['padding-left'] = '50px',  
		})
		:wikitext(headerValue)
	
	headerCell:node(headerContainer)

	local iconContainer = mw.html.create('div')
		:css({
			['position'] = 'absolute',
			['top'] = '1px',
			['right'] = '1px',
		})
		:wikitext('[[ⴰⴼⴰⵢⵍⵓ:Picto_infobox_map.png|95px|alt=ⵜⴰⵢⴽⵓⵏⵜ|link=]]')
		
	headerCell:node(iconContainer)

	if entity then
		local maps = entity:getBestStatements('P242')
		if #maps > 0 then
			local lat, lon = getCoordinates(entity)
			local caption = 'ⴰⴷⵖⴰⵔ ⵏ ' .. headerValue .. ' ⴳ ⵜⴽⴰⵕⴹⴰ'
			
			if lat and lon then
				local coordStr = frame:expandTemplate{ title = 'Coord', args = { lat, lon, display='inline', format='dms' } }
				caption = caption .. '<br /><div style="display:inline-block; margin-top:2px;">[[ⴰⴼⴰⵢⵍⵓ:OOjs_UI_icon_mapPin-progressive.svg|12px]]&nbsp;' .. coordStr .. '</div>'
			end
			
			infobox:tag('tr'):tag('td')
				:attr('colspan', 2)
				:css('text-align', 'center')
				:wikitext('[[ⴰⴼⴰⵢⵍⵓ:' .. maps[1].mainsnak.datavalue.value .. '|260px|frameless]]')
				:tag('div')
				:css({
					['padding-top'] = '3px',
					['font-size'] = '90%',
					['line-height'] = '1.3'
				})
				:wikitext(caption)
		end
	end

	local sectionHeaderStyle = {
		['background-color'] = '#006400',
		['color'] = '#ffffff',
		['text-align'] = 'center',
		['font-weight'] = 'normal',
		['padding'] = '2px',
	}

	local sections = {
		{
			title = 'ⵜⴰⵔⴰⴽⴰⵍⵜ',
			properties = {
				{ 'ⴰⴳⵣⵣⵓⵎ ⵙⴳ', 'ⴰⴳⵣⵣⵓⵎ ⵙⴳ', 'P361' },
				{ 'ⵜⴰⵊⵓⵎⵎⴰ', 'ⵜⴰⵊⵓⵎⵎⴰ', 'P2046' },
				{ 'ⵉⵡⵜⵜⴰ ⴰⴽⴷ', 'ⵉⵡⵜⵜⴰ ⴰⴽⴷ', 'P47' },
				{ 'ⴰⴷⵖⴰⵔ ⴰⵏⴰⴼⵍⵍⴰ', 'ⴰⴷⵖⴰⵔ ⴰⵏⴰⴼⵍⵍⴰ', 'P610' },
				{ 'ⴰⴷⵖⴰⵔ ⴰⵏⴰⴷⴷⴰⵡ', 'ⴰⴷⵖⴰⵔ ⴰⵏⴰⴷⴷⴰⵡ', 'P1589' },
				{ 'ⴰⵙⵉⴼ ⴰⵖⵣⵣⴰⴼ', 'ⴰⵙⵉⴼ ⴰⵖⵣⵣⴰⴼ', 'P2053' },
			}
		},
		{
			title = 'ⵜⴰⵔⴰⵖⵔⴼⵜ',
			properties = {
				{ 'ⵉⵎⵣⴷⴰⵖⵏ', 'ⵉⵎⵣⴷⴰⵖ', 'P1082' },
				{ 'ⵜⴰⵏⵥⵥⵉ', 'ⵜⴰⵏⵥⵥⵉ', 'P7959' },
				{ 'ⵉⵙⵎ ⵏ ⵉⵎⵣⴷⴰⵖⵏ', 'ⵉⵙⵎ ⵉⵎⵣⴷⴰⵖⵏ', 'P1549' },
				{ 'ⵓⵟⵟⵓⵏ ⵏ ⵉⵛⵉⵔⵔⴰⵏ ⴱⵕⵕⴰ ⵏ ⵜⵉⵏⵎⵍ', 'ⵓⵟⵟⵓⵏ ⵏ ⵉⵛⵉⵔⵔⴰⵏ ⴱⵕⵕⴰ ⵏ ⵜⵉⵏⵎⵍ', 'P2573' },
			}
		},
		{
			title = 'ⵓⵎⵍⴰⵏ ⵢⴰⴹⵏⵉⵏ',
			properties = {
				{ 'ⵉⵜⵜⵓⵙⵎⵎⴰ ⵙ', 'ⵉⵜⵜⵓⵙⵎⵎⴰ ⵙ', 'P138' },
				{ 'ⵜⵉⵎⵓⵔⴰ', 'ⵜⵉⵎⵓⵔⴰ', 'P17' },
				{ 'ⴰⴽⵓⴷ', 'ⴰⴽⵓⴷ', 'P421' },
				{ 'ⵖⵓⵔⵙ ⴰⴳⵣⵣⵓⵎ ⵙⴳ', 'ⵖⵓⵔⵙ ⴰⴳⵣⵣⵓⵎ ⵙⴳ', 'P527' },
				{ 'ⴰⴳⵍⴰ ⵏ ⵡⴰⵏⵜⵉⵔⵏⵉⵜ', 'ⴰⴳⵍⴰ ⵏ ⵡⴰⵏⵜⵉⵔⵏⵉⵜ', 'P78' },
				{ 'ⵜⵉⵎⴷⵉⵏⵉⵏ', 'ⵜⵉⵎⴷⵉⵏⵉⵏ', nil },
			}
		}
	}

	for _, section in ipairs(sections) do
		local sectionContent = mw.html.create('')
		local hasData = false

		for _, prop in ipairs(section.properties) do
			local label, param, pid = unpack(prop)
			local value, fromWikidata = getValueOrWikidata(args, param, pid)

			if value then
				hasData = true
				local row = sectionContent:tag('tr')
				row:tag('th')
					:wikitext(label)
					:css({
						['text-align'] = 'left',
						['width'] = '50%',
						['padding-right'] = '10px',
						['font-weight'] = 'normal',
						['background-color'] = '#F3F3F3',
						['vertical-align'] = 'top'
					})
				
				local cell = row:tag('td'):css({['padding'] = '2px', ['width'] = '50%'})
				
				if param == 'ⵜⴰⵏⵥⵥⵉ' then
					value = value .. ' ⵣⴷⵖ/ⴽⵎ²'
				end

				if param == 'ⵜⴰⵊⵓⵎⵎⴰ' then
					value = value .. ' ⴽⵎ²'
				end
				
				if fromWikidata and pid and entityId then
					cell:wikitext(value .. ' [[ⴰⴼⴰⵢⵍⵓ:Edit-icon.svg|15px|link=https://www.wikidata.org/wiki/' .. entityId .. '#' .. pid .. '|ⵙⵏⴼⵍ ⴳ ⵡⵉⴽⵉⴷⴰⵜⴰ]]')
				else
					cell:wikitext(value)
				end
			end
		end

		if hasData then
			infobox:tag('tr'):tag('th')
				:attr('colspan', 2)
				:css(sectionHeaderStyle)
				:wikitext(section.title)
			infobox:node(sectionContent)
		end
	end

	if entityId then
		local lat, lon = getCoordinates(entity)
		if lat and lon then
			local mapFeatures = {}

			table.insert(mapFeatures, {
				type = "ExternalData",
				service = "geoshape",
				ids = entityId,
				properties = {
					["stroke-width"] = 2,
					stroke = "#FF0000",
					["fill-opacity"] = 0.2
				}
			})

			table.insert(mapFeatures, {
				type = "Feature",
				geometry = {
					type = "Point",
					coordinates = { lon, lat }
				},
				properties = {
					--["marker-symbol"] = "star",
					["marker-color"] = "#3d7c35",
					["marker-size"] = "small"
				}
			})

			local mapFrame = frame:extensionTag{
				name = 'mapframe',
				content = mw.text.jsonEncode(mapFeatures),
				args = {
					width = "290",
					height = "190",
					zoom = "2",
					align = "center",
					frameless = "frameless"
				}
			}
			
			infobox:tag('tr'):tag('th')
				:attr('colspan', 2)
				:css(sectionHeaderStyle)
				:wikitext('ⴰⴷⵖⴰⵔ ⴰⵔⴰⴽⴰⵍ') 
			
			infobox:tag('tr'):tag('td')
				:attr('colspan', 2)
				:css('text-align', 'center')
				:wikitext(mapFrame)
		end
	end

	local separatorRow = infobox:tag('tr')
	local separatorCell = separatorRow:tag('td')
		:attr('colspan', 2)
		:css({
			['padding'] = '0',
			['border-top'] = '2px dashed #006400',
		})
	separatorCell:tag('div'):css({['height'] = '1px', ['background-color'] = 'transparent'})

	local currentTitle = mw.title.getCurrentTitle()
	local editSourceUrl = currentTitle:fullUrl({action='edit', section='0'})
	local editVisualUrl = currentTitle:fullUrl({veaction='edit'})
	local templateDocUrl = mw.title.new('ⴰⵍⴱⵓⴹ:ⵜⴰⴼⵍⵓⵖⵎⵉⵙⵜ ⴰⵎⵏⵥⴰⵡ'):fullUrl()

	local footerRow = infobox:tag('tr')
	local footerCell = footerRow:tag('td')
		:attr('colspan', 2)
		:css({
			['padding'] = '0.2em',
			['font-size'] = '85%',
			['text-align'] = 'left',
			['background-color'] = '#f8f8f8',
		})

	local footerContainer = mw.html.create('div')
		:css({
			['display'] = 'flex',
			['justify-content'] = 'space-between',
			['align-items'] = 'center',
		})

	local editLinksSpan = footerContainer:tag('span'):addClass('plainlinks')
	if entityId and entityId ~= '' then
		local wikidataUrl = 'https://www.wikidata.org/wiki/Special:EntityPage/' .. entityId
		editLinksSpan:wikitext('[' .. editSourceUrl .. ' <span style="color:#002bb8;">ⵙⵏⴼⵍ ⴰⵙⴰⴳⵎ</span>] - [' ..
			editVisualUrl .. ' <span style="color:#002bb8;">ⵙⵏⴼⵍ</span>] - [' ..
			wikidataUrl .. ' <span style="color:#002bb8;">ⵡⵉⴽⵉⴷⴰⵜⴰ</span>]')
	else
		editLinksSpan:wikitext('[' .. editSourceUrl .. ' <span style="color:#002bb8;">ⵙⵏⴼⵍ ⴰⵙⴰⴳⵎ</span>] - [' ..
			editVisualUrl .. ' <span style="color:#002bb8;">ⵙⵏⴼⵍ</span>]')
	end

	footerContainer:tag('span')
		:css({['float'] = 'right', ['margin-left'] = '5px'})
		:wikitext('[[ⴰⴼⴰⵢⵍⵓ:Info Simple.svg|18px|ⵥⵕ ⵓⴳⴳⴰⵔ ⵅⴼ ⵡⴰⵍⴱⵓⴹ ⴰⴷ |link=' .. templateDocUrl .. ']]')

	footerCell:node(footerContainer)

	return tostring(infobox)
end

return p