Moduuli:Universal infocard

Tämän moduulin ohjeistuksen voi tehdä sivulle Moduuli:Universal infocard/ohje

--[[
  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;

-- Styles.
local styles = {
	infobox = '',
	error = '',
	title = 'background:#eaecf0; font-weight:bold;',
	original = 'font-style:oblique;',
	label = 'width:9em;',
	text = '',
	media = '',
	split = 'vertical-align:middle; text-align:center;',
	footer = 'background:#eaecf0;',
};
local classes = {
	title = 'infobox-above',
	media = 'infobox-image',
	footer = 'infobox-below'
};
if config and config.styles then
	for key, value in pairs( config.styles ) do
		styles[ key ] = value;
	end
end

function getTemplate( propertyId )
	if config and config.templates and config.templates[ propertyId ] then
		return config.templates[ propertyId ];
	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

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 .. ' style="' .. styles.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 .. ' style="' .. styles.split .. '">';
			result = result .. value2;
			result = result .. '</td>';
		end
		result = result .. '</tr>\n';
	end

	return result;
end

function getLine( value, style, class )
	local result = '';
	if ( value and string.len( value ) ~= 0 ) then
		result = result .. '<tr><td colspan="2" style="text-align:center;' .. ( style or '' ) .. '" class="'.. ( class or '' ) .. '">';
		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 style="' .. styles.label .. '">' .. label .. '</th>';
			result = result .. '<td style="' .. styles.text .. '">\n';
		else
			result = result .. '<tr><td colspan="2" style="' .. styles.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 } );

	mw.logObject( coord, 'coord' );
	mw.logObject( title, 'title' );


	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"
		}
	} ]];

    return args.frame:extensionTag{
    	name = 'mapframe',
    	content = '[' .. mapContent .. ']',
    	args = {
			'frameless',
			align = 'center',
			latitude = coord['latitude'],
			longitude = coord['longitude'],
			zoom = 11,
			width = 300,
			height = 250,
		}
	};
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

-- Filter deprecated claims and returning only preferred ones if present.
function propertyHasEntity( claims, itemId )
	if not claims then
		return false;
	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
				return true;
		end
	end
	
	return false;
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 class="infobox" cellspacing="2"';
	if config and config.styles and config.styles.inferrorobox then
		result = result .. ' style="' .. config.styles.error .. '"';
	end
	result = result .. '>\n';
	result = result .. '<tr><td colspan="2" style="text-align:center">' .. 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 class="infobox" cellspacing="2"';
	if config and config.i18n and config.i18n.dataName then
		result = result .. ' data-name="' .. config.i18n.dataName .. '"';
	end
	if config and config.styles and config.styles.infobox then
		result = result .. ' style="' .. config.styles.infobox .. '"';
	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 = getTemplate( 'title' );
	-- TODO: Make it possible to specify a template for any value, not just Q5.
	if propertyHasEntity( claims.P31, 'Q5' ) then
		local titleTemplateQ5 = getTemplate( 'title_Q5' );
		if titleTemplateQ5 then
			titleTemplate = titleTemplateQ5;
		end
	end

	if entityLabelLang == lang:getCode() then
		label = expandTemplate( frame, titleTemplate, { wdLabel, from = entityId } );
	else
		label = expandTemplate( frame, titleTemplate, { from = entityId } );
	end
	result = result .. getLine( label, styles.title, classes.title );

	---- Original name.
	if claims.P1559 ~= nil then
		result = result .. getLine( expandTemplate( frame, getTemplate( 'P1559' ), { from = entityId } ), styles.original );
	elseif claims.P1705 ~= nil then
		result = result .. getLine( expandTemplate( frame, getTemplate( 'P1705' ), { from = entityId } ), styles.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 } ), styles.media, classes.media );
			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' ), styles.text );
	end

	---- Commons.
	if claims.P373 ~= nil then
		result = result .. getLine( expandTemplate( frame, getTemplate( 'P373' ), { from = entityId } ), styles.footer, classes.footer );
	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;