Module:Universal infocard

Module documentation
warning Warning:This page is shared between multiple wikis.
All changes to this page will be automatically copied to all wikis listed in the left side bar.
To avoid unnecessary page regeneration and server load, changes should be tested on the page's sandbox.

--[[
  Lua code for universal infocard.
]]

local isConfig, config = pcall( require, 'Module:Universal infocard/config' );
if isConfig == false then
	config = {
		skipPropertyIds = {
			P31 = true,
			P279 = true,
		}
	};
end

local p = {};
local lang = mw.getContentLanguage();
local entityId = nil;

-- CSS classes.
local classes = {};
if config and config.classes then
	for key, value in pairs( config.classes ) do
		classes[ key ] = value;
	end
end
	
function getClassString( type )
	local class;
	if classes[type] then
		class = classes[type];
	elseif type ~= '' then
		class = 'infobox-' .. type;
	else
		class = 'infobox';
	end

	if class == '' then
		return '';
	end

	return ' class="'.. class .. '"';
end


function getTemplate( propertyId )
	if config and config.templates then
		if config.templates[ propertyId ] then
			return config.templates[ propertyId ];
		elseif string.match( propertyId, '^title_Q%d+$' ) then
			return nil; -- hack for getTitleTemplate()
		end
	end

	if propertyId == 'title' then
		return '{{PAGENAME}}';
	end

	if propertyId == 'map' then
		return getMap;
	end

	if config and config.templates and config.templates.default then
		return config.templates.default;
	end

	if string.match( propertyId, '^P%d+$' ) then
		return '#statements:' .. propertyId;
	end
	
	return nil;
end

--[[
	Checks entity classes and tries to return a custom template, if one exists.
	Otherwise, returns the base template for the title.
]]
function getTitleTemplate( claims )
	if not claims then
		return getTemplate( 'title' );
	end
	for _, claim in pairs( claims ) do
		if claim.mainsnak
			and claim.mainsnak.datavalue
			and claim.mainsnak.datavalue.value
			and claim.mainsnak.datavalue.value.id
			and claim.mainsnak.datavalue.value.id == itemId then
				local template = getTemplate( 'title:' .. itemId );
				if template then
					return template;
				end;
		end
	end
	
	return getTemplate( 'title' );
end

function expandTemplate( frame, title, args )
	if not title then
		return '';
	elseif type( title ) == 'function' then
		args.frame = frame;
		return title( args )
	elseif type( title ) == 'string' then
		if string.match( title, '^#' ) then
			return frame:callParserFunction{ name = title, args = args };
		elseif string.match( title, '^{' ) then
			return frame:preprocess( title );
		else
			return frame:expandTemplate{ title = title, args = args };
		end
	elseif type( title ) == 'table' then
		local realTitle = title[ 1 ];
		table.remove( title, 1 )
		return expandTemplate( frame, realTitle, title )
	end
end

function splitLine( value1, value2 )
	local result = '';
	if ( value1 and string.len( value1 ) ~= 0 ) or ( value2 and string.len( value2 ) ~= 0 ) then
		result = '<tr>';
		if ( value1 and string.len( value1 ) ~= 0 ) then
			local colspan = '';
			if ( not value2 or string.len( value2 ) == 0 ) then
				colspan = 'colspan="2"';
			end
			result = result .. '<td ' .. colspan .. getClassString( 'split' ) .. '>';
			result = result .. value1;
			result = result .. '</td>';
		end
		if ( value2 and string.len( value2 ) ~= 0 ) then
			local colspan = '';
			if ( not value1 or string.len( value1 ) == 0 ) then
				colspan = 'colspan="2"';
			end
			result = result .. '<td ' .. colspan .. getClassString( 'split' ) .. '>';
			result = result .. value2;
			result = result .. '</td>';
		end
		result = result .. '</tr>\n';
	end

	return result;
end

function getLine( value, class )
	local result = '';
	if ( value and string.len( value ) ~= 0 ) then
		result = result .. '<tr><td colspan="2"'.. getClassString( class ) .. '>';
		result = result .. value;
		result = result .. '</td></tr>\n';
		return result;
	end
	return result;
end

function getValue( label, value )
	local result = '';
	if ( value ~= nil and string.len( value ) ~= 0 ) then
		if label then
			result = result .. '<tr><th' .. getClassString( 'label' ) .. '>' .. label .. '</th>';
			result = result .. '<td' .. getClassString( 'text' ) .. '>\n';
		else
			result = result .. '<tr><td colspan="2"' .. getClassString( 'text' ) .. '>';
		end
		result = result .. value;
		result = result .. '</td></tr>\n';
		return result;
	end
	return result;
end

function getMap( args )
	local entityId = args.entityId or mw.wikibase.getEntityIdForCurrentPage();
	local statements = mw.wikibase.getBestStatements( entityId, 'P625' );
	
	if not statements or
		not statements[ 1 ] or
		not statements[ 1 ].mainsnak or
		statements[ 1 ].mainsnak.snaktype ~= 'value' or
		statements[ 1 ].mainsnak.datavalue.value.globe ~= 'http://www.wikidata.org/entity/Q2'
	then
		return '';
	end

	local coord = statements[ 1 ].mainsnak.datavalue.value;
	local title = expandTemplate( args.frame, getTemplate( 'title' ), { from = entityId } );

	local mapContent = [[ {
		"type": "Feature",
		"geometry": {
			"type": "Point",
			"coordinates": [
				]] .. coord['longitude'] .. [[,
				]] .. coord['latitude'] .. [[
			]
		},
		"properties": {
			"title": "]] .. title .. [[",
			"marker-symbol": "star",
			"marker-color": "#3366cc"
		}
    }, {
		"type": "ExternalData",
		"service": "geoline",
		"ids": "]] .. entityId .. [[",
		"properties": {
			"stroke": "#FF9999"
		}
    }, {
		"type": "ExternalData",
		"service": "geoshape",
		"ids": "]] .. entityId .. [[",
		"properties": {
			"fill": "#FF0000",
			"fill-opacity": 0.1,
			"stroke": "#FF9999"
		}
	} ]];

	local mapWidth = 274;
	local mapHeight = 250;
	if config and config.map then
		if config.map.width then
			mapWidth = config.map.width;
		end
		if config.map.height then
			mapHeight = config.map.height;
		end
	end
    return args.frame:extensionTag{
    	name = 'mapframe',
    	content = '[' .. mapContent .. ']',
    	args = {
			'frameless',
			align = 'center',
			latitude = coord['latitude'],
			longitude = coord['longitude'],
			zoom = 11,
			width = mapWidth,
			height = mapHeight,
		}
	};
end

function renderValue( frame, propertyId, args )
	local tplArgs = { propertyId, from = entityId, nocat = frame.args['nocat'] };

	if args then
	    local k = nil;
	    repeat
	        k = next( args, k );
	        if k then
	            tplArgs[ k ] = args[ k ];
	        end
	    until not k
    end

    return expandTemplate( frame, getTemplate( propertyId ), tplArgs );
end

-- Filter deprecated claims and returning only preferred ones if present.
function filterClaims( entity, propertyId )
	if ( entity.claims == nil or entity.claims[ propertyId ] == nil ) then
		return {};
	end
	local all = entity.claims[ propertyId ];
	local normal = {};
	local preferred = {};
	for _, claim in pairs( all ) do
		local rank = claim.rank or 'normal';
		if ( rank == 'normal' ) then
			table.insert( normal, claim );
		end
		if ( rank == 'preferred' ) then
			table.insert( preferred, claim );
		end
	end
	if ( #preferred > 0 ) then
		return preferred;
	end
	return normal;
end

function propertyLabel( propertyId )
	local label, labelLang = mw.wikibase.getLabelWithLang( propertyId );
	label = lang:ucfirst( label );
	if labelLang ~= lang:getCode() then
		label = '[[d:Property:' .. propertyId .. '|' .. label .. ']]';
	end
	return label;
end

function simpleLabel( entityId )
	local label = mw.wikibase.label( entityId );
	label = lang:ucfirst( label );
	return label;
end

function getErrorMessage( message )
	local result = '<table cellspacing="2"' .. getClassString( 'error' ) .. '>\n';
	result = result .. '<tr><td colspan="2">' .. message .. '</td></tr>\n';
	result = result .. '</table>';
	return result;
end

function p.render( frame )
	local i18n_error_emptyWikidataEntity = '';
	local i18n_error_noWikidataEntity = '';
	if config and config.i18n and config.i18n.error then
		if config.i18n.error.emptyWikidataEntity then
			i18n_error_emptyWikidataEntity = config.i18n.error.emptyWikidataEntity;
		end
		if config.i18n.error.noWikidataEntity then
			i18n_error_noWikidataEntity = config.i18n.error.noWikidataEntity;
		end
	end

	local result = '<table cellspacing="2"' .. getClassString( '' );
	if config and config.i18n and config.i18n.dataName then
		result = result .. ' data-name="' .. config.i18n.dataName .. '"';
	end
	result = result .. '>\n';

	local localImage = nil;

	if ( frame ~= nil and frame:getParent() ~= nil ) then
		local p_frame = frame:getParent();
		if p_frame.args ~= nil then
			-- image under FU only in local
			localImage = p_frame.args.image;
		
			if p_frame.args.from ~= nil and p_frame.args.from ~= '' then
				entityId = p_frame.args.from;
			elseif p_frame.args[ 1 ] ~= nil and string.gmatch( p_frame.args[ 1 ], '^Q\d+$' ) then
				entityId = p_frame.args[ 1 ];
			end
		end
	end

	local wdStatus, entity = pcall( mw.wikibase.getEntity, entityId );
	if wdStatus ~= true or entity == nil then
		return getErrorMessage( i18n_error_noWikidataEntity );
	elseif entity.claims == nil then
		return getErrorMessage( i18n_error_emptyWikidataEntity );
	end
	
	-- TODO: Need to consider how to display class properties (P31, P279, P361, ...).
	local skipPropertyIds = {};
	if config.skipPropertyIds then
		skipPropertyIds = mw.clone( config.skipPropertyIds );
	end

	local claims = entity.claims;
	local order = mw.wikibase.getPropertyOrder() or {};

	-- Header.
	local entityLabel, entityLabelLang  = entity:getLabelWithLang( lang:getCode() );
	local label;

	---- Name.
	local titleTemplate = getTitleTemplate( claims.P31 );
	if entityLabelLang == lang:getCode() then
		label = expandTemplate( frame, titleTemplate, { wdLabel, from = entityId } );
	else
		label = expandTemplate( frame, titleTemplate, { from = entityId } );
	end
	result = result .. getLine( label, 'above' );

	---- Original name.
	if claims.P1559 ~= nil then
		result = result .. getLine( expandTemplate( frame, getTemplate( 'P1559' ), { from = entityId } ), 'original' );
	elseif claims.P1705 ~= nil then
		result = result .. getLine( expandTemplate( frame, getTemplate( 'P1705' ), { from = entityId } ), 'original' );
	end

	---- Flag and COA.
	if claims.P41 or claims.P94 then
		local flag = nil;
		local flagLabel = nil;
		local coa = nil;
		local coaLabel = nil;

		if claims.P41 then
			flag = renderValue( frame, 'P41' );
			if claims.P163 then
				flagLabel = renderValue( frame, 'P163', { text = simpleLabel( 'Q14660' ) } );
			else
				flagLabel = simpleLabel( 'Q14660' );
			end
		end

		if claims.P94 then
			coa = renderValue( frame, 'P94' );
			if claims.P163 then
				coaLabel = renderValue( frame, 'P237', { text = simpleLabel( 'Q14659' ) } );
			else
				coaLabel = simpleLabel( 'Q14659' );
			end
		end

		result = result .. splitLine( flagLabel, coaLabel );
		result = result .. splitLine( flag, coa );
	end

	-- Body.
	local propertyIds = {};
	for propertyId, claim in pairs( entity.claims ) do
		table.insert( propertyIds, propertyId );
	end
	local orderedProperties = mw.wikibase.orderProperties( propertyIds )
	
	local shownProperties = 0
	for i, propertyId in ipairs( orderedProperties ) do
		local propertyClaims = claims[ propertyId ];
		if not skipPropertyIds[ propertyId ]
				and propertyClaims
				and propertyClaims[ 1 ]
				and propertyClaims[ 1 ].mainsnak
				and propertyClaims[ 1 ].mainsnak.datatype
				and propertyClaims[ 1 ].mainsnak.datatype ~= 'external-id'
				and propertyClaims[ 1 ].mainsnak.datatype ~= 'tabular-data'
				and propertyClaims[ 1 ].mainsnak.datatype ~= 'wikibase-property'
		then
			local label = propertyLabel( propertyId );
			if propertyClaims[ 1 ].mainsnak.datatype == 'commonsMedia' then
				result = result .. getLine( renderValue( frame, propertyId, { alt = label } ), 'image' );
			else
				result = result .. getValue( label, renderValue( frame, propertyId ) );
			end
			skipPropertyIds[ propertyId ] = true
			shownProperties = shownProperties + 1
		end
	end

	-- Footer.

	---- Map.
	if claims.P625 ~= nil then
		result = result .. getLine( renderValue( frame, 'map' ), 'text' );
	end

	---- Commons.
	if claims.P373 ~= nil then
		result = result .. getLine( expandTemplate( frame, getTemplate( 'P373' ), { from = entityId } ), 'below' );
	end

	result = result .. '</table>';

	-- Coords.
	if claims.P625 ~= nil then
		result = result .. renderValue( frame, 'P625', { display = 'title' } );
	end
	
	-- Tracking category.
	if config and config.categories and config.categories['few-properties-shown'] then
		if shownProperties < 4 then
			result = result .. '[[Category:' .. config.categories['few-properties-shown'] .. '|' .. shownProperties .. ']]'
		end
	end

	return result; 
end

return p;