Məzmuna keç

Modul:Wikidata/qualifiers

Vikipediya, azad ensiklopediya

Bu modulun sənədləşdirmə səhifəsi Modul:Wikidata/qualifiers/doc səhifəsində yaradıla bilər

local config = require( 'Module:Wikidata/qualifiers/config' )
local Ordinal = require( 'Modul:Ordinal' )
local p = {}

function p.getValues( statement, property )
	return statement and statement.qualifiers and statement.qualifiers[ property ] or {}
end

function p.getFirstWithDataValue( statement, property )
	for _, snak in ipairs( p.getValues( statement, property ) ) do
		if snak.snaktype ~= 'novalue' and snak.snaktype ~= 'somevalue' and snak.datavalue then
			return snak
		end
	end
end

function p.getTimeBoundariesFromQualifier( context, statement, property )
	local left, right
	for _, snak in ipairs( p.getValues( statement, property ) ) do
		local dates = context.parseTimeBoundariesFromSnak( snak )
		if not dates then return nil end
		left = left and math.min( left, dates[ 1 ] ) or dates[ 1 ]
		right = right and math.max( right, dates[ 2 ] ) or dates[ 2 ]
	end
	if left and right then return { left, right } end
end

function p.getTimeBoundariesFromQualifiers( context, statement, properties, pointProperty )
	for _, property in ipairs( properties or config.boundaryPriority ) do
		local dates = p.getTimeBoundariesFromQualifier( context, statement, property )
		if dates then return property == pointProperty and { dates[ 1 ], dates[ 1 ] } or dates end
	end
end

function p.getLabelTimeBoundaries( context, statement )
	return p.getTimeBoundariesFromQualifiers( context, statement, config.labelBoundaryPriority, 'P582' )
end

function p.getSourcingCircumstances( statement )
	local result, seen = {}, {}
	for _, snak in ipairs( p.getValues( statement, 'P1480' ) ) do
		local value = snak.snaktype ~= 'novalue' and snak.snaktype ~= 'somevalue' and
			snak.datavalue and snak.datavalue.value
		local id = type( value ) == 'table' and value.id
		if id and not seen[ id ] then
			seen[ id ] = true
			table.insert( result, id )
		end
	end
	return result
end

function p.getI18n( defaults, overrides )
	local result = {}
	for _, values in ipairs( { config.circumstances, defaults or {}, overrides or {} } ) do
		for key, value in pairs( values ) do result[ key ] = value end
	end
	return result
end

function p.applyCircumstances( options, result, circumstances )
	if not result or result == '' then return result end
	local seen = {}
	for _, id in ipairs( circumstances or {} ) do
		if not seen[ id ] then
			seen[ id ] = true
			local prefix = options.i18n and options.i18n[ id ]
			if prefix == nil then prefix = config.circumstances[ id ] end
			if type( prefix ) == 'string' then result = prefix .. result end
		end
	end
	return result
end

local function yearFromSnak( snak )
	local value = snak and snak.datavalue and snak.datavalue.type == 'time' and snak.datavalue.value
	if type( value ) == 'table' and type( value.time ) == 'string' then
		return tonumber( string.match( value.time, '^([+-]?%d+)%-' ) )
	end
end

function p.getPeriod( statement )
	return {
		from = yearFromSnak( p.getFirstWithDataValue( statement, 'P580' ) ),
		to = yearFromSnak( p.getFirstWithDataValue( statement, 'P582' ) ),
		point = yearFromSnak( p.getFirstWithDataValue( statement, 'P585' ) ),
		qualifiers = statement.qualifiers,
	}
end

function p.renderRange( range, options )
	options = options or {}
	if options.endyearonly and range.to and not range.from then return tostring( range.to ) end
	if options.startyearonly and range.from and not range.to then return tostring( range.from ) end
	if range.from and range.to then
		if range.from == range.to then return tostring( range.from ) end
		return range.from .. config.rangeSeparator .. range.to
	elseif range.from then
		return ( type( range.from ) == 'number' and Ordinal._ordinal( range.from ) or range.from ) .. config.fromSuffix
	elseif range.to then
		return ( type( range.to ) == 'number' and Ordinal._ordinal( range.to ) or range.to ) .. config.toSuffix
	elseif range.point then
		return tostring( range.point )
	end
end

local function periodStart( range )
	return range.from or range.point or 0
end

local function sortPeriods( ranges )
	table.sort( ranges, function( a, b )
		local first, second = periodStart( a ), periodStart( b )
		return first < second or first == second and a.index < b.index
	end )
end

local function parenthesize( text, options )
	if text == '' then return '' end
	local style = options.qualifierStyle
	if style == nil then style = config.style end
	if style == false or style == '' then return ' (' .. text .. ')' end
	return ' <span style="' .. style .. '">(' .. text .. ')</span>'
end

local function formatValues( context, options, statement, property )
	local snaks = p.getValues( statement, property )
	if #snaks == 0 then return '', false end
	local params = context.getPropertyParams( property, snaks[ 1 ].datatype, {} )
	params.property = property
	params.i18n = options.i18n
	params.entity, params.frame = options.entity, options.frame
	local result = {}
	for _, snak in ipairs( snaks ) do
		local year = not params.invisible and options.qualifierPrecision ~= 'full' and yearFromSnak( snak )
		local value = year and tostring( year ) or context.formatSnak( params, snak )
		if value and value ~= '' then table.insert( result, value ) end
	end
	return table.concat( result, config.separator ), params.invisible
end

function p.appendQualifiers( context, options, statement, result )
	if not result or result == '' then return result end
	local first, firstInvisible = formatValues( context, options, statement, options.firstqualifier )
	local second, secondInvisible = formatValues( context, options, statement, options.secondqualifier )
	if firstInvisible then result, first = result .. first, '' end
	if secondInvisible then result, second = result .. second, '' end
	if first ~= '' or second ~= '' then
		local range = first == second and first or first .. config.rangeSeparator .. second
		result = result .. parenthesize( range, options )
	end
	if first == '' and options.qualifier and options.qualifier ~= options.firstqualifier and
		options.qualifier ~= options.secondqualifier
	then
		local value, invisible = formatValues( context, options, statement, options.qualifier )
		result = result .. ( invisible and value or parenthesize( value, options ) )
	end
	return result
end

function p.formatStatement( context, options, statement )
	local valueOptions = context.cloneOptions( options )
	valueOptions.qualifiers = statement.qualifiers
	local result = context.formatSnak( valueOptions, statement.mainsnak, p.getSourcingCircumstances( statement ) )
	result = p.appendQualifiers( context, valueOptions, statement, result )
	if result and result ~= '' and options.references then
		result = result .. context.formatRefs( options, statement )
	end
	return result
end

local function entityId( statement )
	local snak = statement and statement.mainsnak
	local value = snak and snak.snaktype == 'value' and snak.datavalue and
		snak.datavalue.type == 'wikibase-entityid' and snak.datavalue.value
	return type( value ) == 'table' and value.id or nil
end

local function groupClaims( claims, preferredOnly )
	local groups, order = {}, {}
	for index, statement in ipairs( claims or {} ) do
		local id = entityId( statement )
		if id and statement.rank ~= 'deprecated' and ( not preferredOnly or statement.rank == 'preferred' ) then
			if not groups[ id ] then
				groups[ id ] = { snak = statement.mainsnak, claimId = statement.id, ranges = {}, statements = {}, index = index }
				table.insert( order, id )
			end
			local range = p.getPeriod( statement )
			range.index = index
			table.insert( groups[ id ].ranges, range )
			table.insert( groups[ id ].statements, statement )
		end
	end
	for _, id in ipairs( order ) do sortPeriods( groups[ id ].ranges ) end
	return groups, order
end

local function getLabelQualifiers( context, ranges )
	local latest, qualifiers
	for _, range in ipairs( ranges ) do
		local dates = p.getLabelTimeBoundaries( context, { qualifiers = range.qualifiers } )
		if dates and ( not latest or dates[ 1 ] > latest ) then
			latest, qualifiers = dates[ 1 ], range.qualifiers
		end
	end
	return qualifiers
end

local function renderGroups( context, options, groups, order, merged )
	table.sort( order, function( a, b )
		local first, second = periodStart( groups[ a ].ranges[ 1 ] ), periodStart( groups[ b ].ranges[ 1 ] )
		return first < second or first == second and groups[ a ].index < groups[ b ].index
	end )
	local items = {}
	for _, id in ipairs( order ) do
		local group = groups[ id ]
		local valueOptions = context.cloneOptions( options )
		valueOptions.qualifiers = getLabelQualifiers( context, group.ranges )
		local circumstances = #group.statements == 1 and p.getSourcingCircumstances( group.statements[ 1 ] ) or nil
		local label = context.formatSnak( valueOptions, group.snak, circumstances )
		if label and label ~= '' then
			local parts, seen = {}, {}
			for _, range in ipairs( group.ranges ) do
				local text = p.renderRange( range, options )
				if text and not seen[ text ] then
					seen[ text ] = true
					table.insert( parts, text )
				end
				if range.from and not range.to then break end
			end
			label = label .. parenthesize( table.concat( parts, config.separator ), options )
			if options.references then
				label = label .. context.formatMergedRefs( options, group.statements )
			end
			local property = string.upper( string.match( options.property, '^[Pp]%d+' ) )
			label = context.wrapStatement( label, property, group.claimId )
			table.insert( items, label )
		end
	end
	if #items == 0 then return '' end
	local before = options.before or ( merged and '<ul><li>' or '' )
	local separator = options.separator or ( merged and '</li><li>' or config.separator )
	local after = options.after or ( merged and '</li></ul>' or '' )
	return before .. table.concat( items, separator ) .. after
end

function p.formatMergedProperty( context, options )
	local claims = context.selectClaims( options, options.property .. ( options.rank or '[!rank:deprecated]' ) )
	local groups, order = groupClaims( claims )
	return renderGroups( context, options, groups, order, true )
end

function p.formatAllPreferredClaims( context, options, claims, order )
	local groups, preferredOrder = groupClaims( claims, true )
	local selected = {}
	for _, id in ipairs( order or preferredOrder ) do
		if groups[ id ] then table.insert( selected, id ) end
	end
	return renderGroups( context, options, groups, selected, false )
end

function p.formatMostRecentProperty( context, options )
	local claims = context.selectClaims( options, options.property .. '[rank:preferred, rank:normal]' )
	if not claims or #claims == 0 then return '' end
	if options.allpreferred then
		local _, order = groupClaims( claims, true )
		if #order > 1 then return p.formatAllPreferredClaims( context, options, claims, order ) end
	end
	local latest, maxTimestamp
	for _, statement in ipairs( claims ) do
		if statement.rank ~= 'deprecated' then
			local timestamp
			for _, property in ipairs( { 'P585', 'P580' } ) do
				local snak = p.getFirstWithDataValue( statement, property )
				if snak then timestamp = context.parseTimeFromSnak( snak ) end
				if timestamp then break end
			end
			if timestamp and ( not maxTimestamp or timestamp > maxTimestamp ) then
				latest, maxTimestamp = statement, timestamp
			end
		end
	end
	if not latest then return context.formatPropertyDefault( context, options ) end
	local id = entityId( latest )
	if id then
		local groups = groupClaims( claims )
		return renderGroups( context, options, groups, { id }, false )
	end
	local range = p.getPeriod( latest )
	range.index = 1
	local groups = { { snak = latest.mainsnak, claimId = latest.id, ranges = { range }, statements = { latest }, index = 1 } }
	return renderGroups( context, options, groups, { 1 }, false )
end

function p.formatQuantityWithDateClaim( context, options, statement )
	local valueOptions = context.cloneOptions( options )
	valueOptions.qualifier = 'P585'
	valueOptions.qualifierPrecision = 'full'
	valueOptions.qualifierStyle = false
	return p.formatStatement( context, valueOptions, statement )
end

function p.formatDateRange( first, second )
	local function year( value )
		if not value or value == '' then return nil end
		local text = tostring( value ):gsub( '%[%[', '' ):gsub( '%]%]', '' )
		local found = text:match( '(%d%d%d%d)' )
		if found == '0369' then return '?' end
		return found and tonumber( found ) or text:match( '^%s*(.-)%s*$' )
	end
	return p.renderRange( { from = year( first ), to = year( second ) } ) or ''
end

p.formatPropertyWithMostRecentClaimAndIndicator = p.formatMostRecentProperty

return p