Hopp til innhold

Modul:Sandkasse/jeblad/Modul:External links

Fra Wikipedia, den frie encyklopedi
Sideversjon per 9. jul. 2016 kl. 16:41 av Jeblad (diskusjon | bidrag) (added isDeprecated)
Moduldokumentasjon


require('Module:No globals')

local contLang = mw.language.getContentLanguage()
local conf = require 'Module:Sandkasse/jeblad/Modul:Official links/conf'(contLang:getCode())
local msgs = require 'Module:Sandkasse/jeblad/Modul:Official links/messages'(contLang:getCode())

---
-- The ''pid'' is used to identify the proper messages for the agregate.
-- @param pid string identifier for the property to aggregate
-- @param list table of entries to format as a propert aggregate
-- @return string of aggregated content
local function buildList( pid, list  )
	assert(pid, 'missing argument string "pid"')
	assert(list, 'missing argument table "list"')
	if #list == 0 then
		return ''
	elseif #list == 1 then
		return list[1]
	end
	local last = table.remove(list)
	local str = table.concat(list, msgs:g('initial-' .. pid .. '-combiner', 'initial-default-combiner'):plain())
	return msgs:g('final-' .. pid .. '-combiner', 'final-default-combiner'):params(str, last):plain()
end

---
-- @param content string containing whatever to be rendered
-- @param marker string to insert as a part of the overall marker
-- @return string of wrapped up content
-- @todo messy solution, refactor
local function wrap( content, marker )
	if content == '' or content == nil then
		return nil
	end
return ''
--[[
	return '<span class="mw-collapsible mw-collapsed" id="mw-customcollapsible-'..marker..'"><span class="mw-collapsible-content">' .. content .. '</span>'
		.. ' <span class="mw-customtoggle-'..marker..'" data-expandtext="{{int:show}}" data-collapsetext="{{int:hide}}" /></span>'
--]]
end

---
-- A simple memoize solution.
-- @field id label a string &rarr; string relation 
-- @todo Use a proper memoize pattern, like [http://www.lua.org/pil/17.1.html Programming in Lua: 17.1 – Memoize Functions]
-- or [https://github.com/kikito/memoize.lua/blob/master/memoize.lua kikito/memoize.lua].
-- Note also [http://lua-users.org/wiki/CurriedMemoization Curried Memoization].
local labels = {}

--- 
-- This is a general function, but is used for lookup of property labels.
-- @param id string identifier for the property or item to query
-- @return string label from the item
local function getEntityLabel( id )
	local entity = mw.wikibase.getEntity( id )
	if entity then
		return entity:getLabel() -- @fixme only uses a single language, should use fallback chain
	end
end

---
-- This is a general function, but is used for lookup of property labels.
-- @param id string identifier for the property or item to query
-- @return string label from the memoized pattern or the entity itself
-- @todo This is a simple solution to the memoize problem, use a proper pattern.
local function findEntityLabel( id )
	if not labels[id] then
		local label = getEntityLabel( id )
		if label then
			labels[id] = label -- labels is an outer structure
		end
	end
	return labels[id]
end

---
-- These are the formatters for the main snaks in the statements. Usually they
-- will be the at the middle level, with formatting at the template level above
-- them and qualifiers below them.
-- @field datatype function 
local mainFormatter = {}

--- Main formatter for a datavalue
-- @param pid string identifier for the property (not in use)
-- @param datavalue table representing the structure
-- @return string representing the formatted main value
mainFormatter['string'] = function( pid, datavalue )
	-- Given the ''pid'' the datavalue should be of type "string".
	if not (datavalue['type'] == 'string') then
		return nil
	end
	return datavalue.value
end

---
-- These are the weights of the main statements. Weights are used for figuring
-- out which statements are more important than other.
-- @field datatype function 
local mainWeight = {}

--- Main weight for a string datavalue
-- @param pid string identifier for the property (not in use)
-- @param datavalue table representing the structure
-- @return number representing the weight of the main value
mainWeight['string'] = function( pid, datavalue )
	-- Given the ''pid'' the datavalue should be of type "string".
	if not (datavalue['type'] == 'string') then
		return 0
	end
	return conf:fragmentWeight(datavalue.value)
end

---
-- These are the formatters for the auxilary snaks in the statements, that is
-- whats usually called qualifiers. Usually they will be the at the lowest level,
-- with formatting at the template level and main snaks above them.
-- @field datatype function 
local qualFormatter = {}

--- Qualifier (auxilary) formatter for a datavalue
-- @param pid string identifier for the property (not in use)
-- @param datavalue table representing the structure
-- @return string representing the formatted auxilary value
qualFormatter['wikibase-entityid'] = function( pid, datavalue )
	-- Given the ''pid'' the datavalue should be of type "wikibase-entityid" and
	-- entity-type should be "item".
	if not (datavalue['type'] == 'wikibase-entityid' and datavalue.value['entity-type'] == 'item') then
		return nil
	end
	return findEntityLabel( 'Q'..datavalue.value["numeric-id"] )
end

local qualPriority = {}
qualPriority['wikibase-entityid'] = function( pid, datavalue )
	if datavalue['type'] ~= 'wikibase-entityid' then
		return nil
	end
	if datavalue.value['entity-type'] ~= 'item' then
		return nil
	end
	return conf:priority( 'Q'..datavalue.value["numeric-id"] ) or false
end

---
-- These are the weights of the qualifier (auxilary) statements. Weights are used
-- for figuring out which statements are more important than other.
-- @field datatype function 
local qualWeight = {}

--- Qualifier weight for a string datavalue
-- @param pid string identifier for the property (not in use)
-- @param datavalue table representing the structure
-- @return number representing the weight of the main value
qualWeight['wikibase-entityid'] = function( pid, datavalue )
	-- Given the ''pid'' the datavalue should be of type "wikibase-entityid" and
	-- entity-type should be "item".
	if not (datavalue['type'] == 'wikibase-entityid' and datavalue.value['entity-type'] == 'item') then
		return 0
	end
	return conf:languageWeight(datavalue.value)
end

local main = {}
main.P856 = {
	types = {
		snaktype = 'value',
		datatype = 'url',
	},
	formatter = mainFormatter['string'],
	weight = mainWeight['string']
}
main.P1019 = {
	types = {
		snaktype = 'value',
		datatype = 'url',
	},
	formatter = mainFormatter['string'],
	weight = mainWeight['string']
}
main.P1581 = {
	types = {
		snaktype = 'value',
		datatype = 'url',
	},
	formatter = mainFormatter['string'],
	weight = mainWeight['string']
}


local qual = {}
qual.P407 = {
	types = {
		snaktype = 'value',
		datatype = 'wikibase-item',
	},
	formatter = qualFormatter['wikibase-entityid'],
	preferred = qualPriority['wikibase-entityid'],
	weight = qualWeight['wikibase-entityid']
}

local qorder = {'P407'}

local function isDeprecated( self )
	return self['type'] == 'statement' and self['rank'] == 'deprecated'
end

local p = {}

---
-- @param pid string identifying which property-set to get
-- @param qid {string|nil} identifying which item to get, or to use the default one
-- @return table of head entries, those that somehow are deemed more important
-- @return table of tail entries, those that somehow are deemed less important
function p.findMainLinks(pid, qid)
	local head = {}
	local tail = {}
	local maxWeight = -100
	local entity = mw.wikibase.getEntityObject( qid )
	if not entity or not entity.claims then
		return head, tail
	end
	local statements = pid and entity.claims[pid]
	if not statements then
		return head, tail
	end
	for _, claim in ipairs( statements ) do
		-- to avoid deep tests
		claim = claim or {}
		local valid = true
		if claim:isDeprecated() then
			valid = valid and false
		end
		local mainsnak = claim.mainsnak or {}
		if not mainsnak or not main[pid] then
			valid = valid and false
		end
		if ((main[pid] and mainsnak.snaktype ~= main[pid].types.snaktype)
			or (main[pid] and mainsnak.datatype ~= main[pid].types.datatype))
		then
			valid = valid and false
		end
		local weight = (claim['rank'] == 'preferred' and 100)
			or (claim['rank'] == 'normal' and 50)
			or 0
		if valid then
		--	local preferred -- = claim['rank'] == 'preferred'
			weight = weight + main[pid].weight(pid, mainsnak.datavalue)
			local mainStr = main[pid].formatter(pid, mainsnak.datavalue)
			local optionals = {}
			local qualifiers = claim.qualifiers or {}
			for _, qualid in ipairs( qorder ) do
				local text = nil
				if qualifiers[qualid] then
					local items = {}
					for _, qualsnak in ipairs( qualifiers[qualid] ) do
						if qualsnak and qual[qualid] then
							if not (qualsnak.snaktype ~= qual[qualid].types.snaktype
								or qualsnak.datatype ~= qual[qualid].types.datatype)
							then
								items[1+#items] = qual[qualid].formatter(qualsnak.property, qualsnak.datavalue)
								weight = weight + qual[qualid].weight(qualsnak.property, qualsnak.datavalue)
							--	preferred = preferred or qual[qualid].preferred(qualsnak.property, qualsnak.datavalue)
							end
						end
					end
					text = buildList(qualid, items)
					if qualid == 'P407' and (not text or text == '') then
						-- this should never happen, unless it will be possible to add empty qualifiers
						text = msgs:g('qualifier-'.. qualid..'-empty', 'qualifier-default-empty'):plain()
					end
				else
					if qualid == 'P407' then
						text = conf:guess(mainStr)
					end
					if not text then
						text = msgs:g('qualifier-'.. pid..'-missing-'..qualid,
								'qualifier-default-missing-'..qualid,
								'qualifier-default-missing'):plain()
					end
				end
				if text then
					text = msgs:g('ext-link-' .. claim['rank']):params(mainStr, text):plain()
					if weight > maxWeight then
						maxWeight = weight
						for _,v in ipairs(head) do
							table.insert(tail, v)
						end
						head = { text }
					elseif weight == maxWeight then
						table.insert(head, text)
					else
						table.insert(tail, text)
					end
				end
			end
		--	if #optionals > 0 then
		--		mainStr = buildList(pid, optionals)
		--	end
			if #head == 0 then
				head = tail
				tail = {}
			end
		end
	end
	return head, tail
end

function p.links( frame )
	local items = {}
	local counts = {}
	for _,v in ipairs(frame.args) do
		local _, _, ch, num= v:find("^%s*([pP])(%d+)%s*$")
		if ch then
			local pid = ch:upper()..num
			local label = findEntityLabel( pid )
			if not counts[pid] then
				counts[pid] = 0
			end
			if label then
				local head, tail = p.findMainLinks(pid)
				if (head and #head > 0) or (tail and #tail > 0) then
					counts[pid] = counts[pid] + 1
				end
				if head and #head > 0 then
					items[1+#items] = { contLang:ucfirst( label ), buildList('', head), wrap(buildList('', tail), pid) }
				elseif tail and #tail > 0 then
					items[1+#items] = { contLang:ucfirst( label ), buildList('', tail) }
				end
			end
		end
	end
	for i,v in ipairs(items) do
		items[i] = 
			((#v >=3 and #v[3])
				and msgs:g(i==1 and 'first-list-item-with-additional' or 'rest-list-item-with-additional')
				or msgs:g(i==1 and 'first-list-item' or 'rest-list-item'))
			:params(unpack(v))
			:plain()
	end
	local cats = {}
	for k,v in pairs(counts) do
		cats[1+#cats] = '[[Category:' .. msgs:g(counts[k]==0 and 'cat-exclusion' or 'cat-inclusion'):params(findEntityLabel( k )):plain() .. ']]'
	end
	if #items > 0 then
		return table.concat(items, "\n") .. table.concat(cats, "")
	end
	return "''" .. msgs:g('no-links-available'):plain() .. "''" .. table.concat(cats, "")
end

return p