Aller au contenu

Poképédia subit actuellement quelques soucis techniques !
L'équipe est au courant des problèmes et fait le nécessaire pour les arranger le plus rapidement possible. Merci de votre compréhension !

Module:Liste des cartes d'un sujet

De Poképédia

La documentation pour ce module peut être créée à Module:Liste des cartes d'un sujet/doc

local p = {}

local PROP_JEU = 'P139'
local PROP_EXTENSION = 'P140'
local PROP_SOUS_EXTENSION = 'P141'
local PROP_DATE_SORTIE = 'P142'
local PROP_NUMERO_CARTE = 'P143'
local PROP_NUMERO_MAX_SET = 'P144'
local PROP_SUJET_REPRESENTE = 'P145'
local PROP_SUJET_SECONDAIRE = 'P146'

local DEFAULT_PRIMARY_LIMIT = 10
local DEFAULT_SECONDARY_LIMIT = 5
local DEFAULT_ILLUSTRATOR_LIMIT = 50
local DEFAULT_TRAINER_LIMIT = 10
local MAX_MULTI_LOOKUP_LIMIT = 5000

local JCC_ITEM = 'Q28777'
local JCCP_ITEM = 'Q10404'

local reverse_lookup_library = nil
local card_renderer = nil

local DESC_ORDER = {
	{ property = PROP_DATE_SORTIE, kind = 'time', order = 'desc', nulls = 'last' },
	{ property = PROP_EXTENSION, kind = 'item-title', order = 'asc', nulls = 'last' },
	{ property = PROP_NUMERO_CARTE, kind = 'natural-string', order = 'asc', nulls = 'last' },
	{ kind = 'page-title', order = 'asc', nulls = 'last' },
}

local function trim( value )
	if value == nil then
		return ''
	end

	return mw.text.trim( tostring( value ) )
end

local function get_args( frame )
	local args = {}

	local function merge( source )
		if not source then
			return
		end

		for key, value in pairs( source ) do
			if value ~= nil then
				args[key] = value
			end
		end
	end

	merge( frame:getParent() and frame:getParent().args )
	merge( frame.args )

	return args
end

local function get_reverse_lookup_library()
	if reverse_lookup_library then
		return reverse_lookup_library
	end

	if mw.pokepediawb then
		reverse_lookup_library = mw.pokepediawb
		return reverse_lookup_library
	end

	local ok, lib = pcall( require, 'mw.pokepediawb' )
	if ok and type( lib ) == 'table' then
		reverse_lookup_library = lib
		return reverse_lookup_library
	end

	return nil
end

local function get_card_renderer()
	if card_renderer then
		return card_renderer
	end

	local ok, module = pcall( require, 'Module:Carte JCC/wb' )
	if ok and type( module ) == 'table' and type( module.main ) == 'function' then
		card_renderer = module
		return card_renderer
	end

	return nil
end

local function sort_number_key( raw_value )
	local value = mw.ustring.upper( trim( raw_value ) )
	value = mw.ustring.gsub( value, '%s+', '' )

	if value == '' then
		return '~~~~~~'
	end

	local prefix, digits, suffix = mw.ustring.match( value, '^([^0-9]*)([0-9]+)(.*)$' )
	if digits then
		return string.format( '%s%06d%s', prefix, tonumber( digits ) or 0, suffix )
	end

	return value
end

local function descending_date_key( time_value )
	if type( time_value ) ~= 'string' then
		return -1
	end

	local year, month, day = time_value:match( '^%+?(%-?%d+)%-(%d%d)%-(%d%d)T' )
	if not year then
		return -1
	end

	return ( tonumber( year ) or 0 ) * 10000
		+ ( tonumber( month ) or 0 ) * 100
		+ ( tonumber( day ) or 0 )
end

local function sort_cards_desc( cards )
	table.sort( cards, function( left, right )
		local left_date = descending_date_key( left.release_time )
		local right_date = descending_date_key( right.release_time )
		if left_date ~= right_date then
			return left_date > right_date
		end

		local left_extension = mw.ustring.lower( left.extension_title or '' )
		local right_extension = mw.ustring.lower( right.extension_title or '' )
		if left_extension ~= right_extension then
			return left_extension < right_extension
		end

		local left_number = sort_number_key( left.card_number )
		local right_number = sort_number_key( right.card_number )
		if left_number ~= right_number then
			return left_number < right_number
		end

		return mw.ustring.lower( left.page_title ) < mw.ustring.lower( right.page_title )
	end )
end

local function classify_games( cards )
	local has_jcc = false
	local has_jccp = false

	for _, card in ipairs( cards ) do
		if card.game_item_id == JCC_ITEM then
			has_jcc = true
		elseif card.game_item_id == JCCP_ITEM then
			has_jccp = true
		end
	end

	return has_jcc, has_jccp
end

local function get_subject_titles( args )
	local titles = {}
	local seen = {}

	local function add_title( raw_title )
		local title = trim( raw_title )
		if title == '' or seen[title] then
			return
		end

		seen[title] = true
		titles[#titles + 1] = title
	end

	add_title( args.page )
	add_title( args[1] )
	add_title( args[2] )
	add_title( args[3] )

	if #titles == 0 then
		add_title( mw.title.getCurrentTitle().text )
	end

	return titles
end

local function get_subject_item_ids( args, titles )
	local item_ids = {}
	local seen = {}
	local explicit_item = trim( args.item )

	local function add_item_id( item_id )
		if item_id == '' or seen[item_id] then
			return
		end

		seen[item_id] = true
		item_ids[#item_ids + 1] = item_id
	end

	if explicit_item ~= '' then
		add_item_id( explicit_item )
		return item_ids
	end

	for _, title in ipairs( titles ) do
		add_item_id( mw.wikibase.getEntityIdForTitle( title ) or '' )
	end

	return item_ids
end

local function get_numeric_arg( args, key )
	local raw_value = trim( args[key] )
	if raw_value == '' then
		return nil
	end

	local numeric_value = tonumber( raw_value )
	if not numeric_value then
		return nil
	end

	numeric_value = math.floor( numeric_value )
	if numeric_value < 0 then
		return nil
	end

	return numeric_value
end

local function normalize_game_filter( args )
	local raw_value = mw.ustring.lower( trim( args.jeu ) )
	if raw_value == 'jcc' or raw_value == 'jeu de cartes à collectionner' then
		return JCC_ITEM, 'JCC'
	end

	if raw_value == 'jccp' or raw_value == 'jeu de cartes à collectionner pokémon pocket' then
		return JCCP_ITEM, 'JCCP'
	end

	return nil, nil
end

local function build_query_constraint( property_label, titles, game_query_name )
	local clauses = {}

	for _, title in ipairs( titles ) do
		local clause = string.format( '[[%s::%s]]', property_label, title )
		if game_query_name then
			clause = string.format( '[[Jeu de la carte::%s]] %s', game_query_name, clause )
		end
		clauses[#clauses + 1] = clause
	end

	return table.concat( clauses, ' OR ' )
end

local function escape_special_ask_segment( value )
	local escaped = tostring( value or '' )

	escaped = escaped:gsub( '%-', '-2D' )
	escaped = escaped:gsub( ' ', '-20' )
	escaped = escaped:gsub( '%[', '-5B' )
	escaped = escaped:gsub( '%]', '-5D' )
	escaped = escaped:gsub( '<', '-3C' )
	escaped = escaped:gsub( '>', '-3E' )
	escaped = escaped:gsub( '/', '-2F' )

	return escaped
end

local function build_query_link_url( query_constraint, text )
	local page_title = table.concat( {
		'Spécial:Requêter',
		escape_special_ask_segment( query_constraint ),
		'mainlabel=',
		'order=desc,asc,asc,asc',
		'sort=' .. escape_special_ask_segment( 'Date de sortie de la carte,Extension,Numéro de carte formaté,' ),
		'offset=0',
		'format=template',
		'link=none',
		'intro=' .. escape_special_ask_segment( '<div class="liste-cartes">' ),
		'outro=' .. escape_special_ask_segment( '</div>' ),
		'searchlabel=' .. escape_special_ask_segment( text ),
		'default=' .. escape_special_ask_segment( text ),
		'template=' .. escape_special_ask_segment( 'Carte JCC' ),
	}, '/' )

	if mw.uri.localUrl then
		return tostring( mw.uri.localUrl( page_title ) )
	end

	return tostring( mw.uri.fullUrl( page_title ) ):gsub( '^https?://[^/]+', '' )
end

local function escape_link_label( text )
	local escaped = tostring( text or '' )
	escaped = escaped:gsub( '%[', '&#91;' )
	escaped = escaped:gsub( '%]', '&#93;' )
	return escaped
end

local function render_query_link( frame, property_label, titles, text, game_query_name )
	local query_constraint = build_query_constraint( property_label, titles, game_query_name )
	local url = build_query_link_url( query_constraint, text )

	return '<span class="smw-template-furtherresults">'
		.. frame:preprocess( '[[:' .. url:gsub( '^/', '' ) .. '|' .. escape_link_label( text ) .. ']]' )
		.. '</span>'
end

local function render_associated_lists( frame, property_label, titles, has_jcc, has_jccp, game_item_id )
	if game_item_id or not has_jcc or not has_jccp then
		return ''
	end

	local jcc_link = render_query_link( frame, property_label, titles, 'JCC', 'JCC' )
	local jccp_link = render_query_link( frame, property_label, titles, 'JCC Pokémon Pocket', 'JCCP' )

	return '<div style="line-height:125%; padding:5px 0px"><b>Listes de cartes associées&nbsp;:</b> <span style="white-space:nowrap">'
		.. jcc_link .. ' • ' .. jccp_link .. '</span></div>'
end

local function render_card_tiles( frame, cards, size )
	local renderer = get_card_renderer()
	if not renderer then
		error( "Module:Carte JCC/wb indisponible." )
	end

	local pieces = {}

	for index, card in ipairs( cards ) do
		local template_args = { [1] = card.page_title }
		if size then
			template_args.taille = tostring( size )
		end

		pieces[#pieces + 1] = renderer.main( frame:newChild { args = template_args } )

		if index < #cards then
			pieces[#pieces + 1] = '<div style="display:inline-block"></div>'
		end
	end

	return '<div class="liste-cartes">' .. table.concat( pieces ) .. '</div>'
end

local function render_view_all( frame, property_label, titles, has_more, game_query_name )
	if not has_more then
		return ''
	end

	local link = render_query_link( frame, property_label, titles, '[Voir toutes les cartes]', game_query_name )
	return '<div style="line-height:125%; padding:5px 0px">' .. link .. '</div>'
end

local function render_empty_block()
	return '<div style="text-align:center"><div class="liste-cartes">Pas de carte</div></div>'
end

local function render_cards_block( frame, property_label, titles, cards, size, has_more, game_item_id, game_query_name, has_jcc, has_jccp )
	return '<div style="text-align:center">'
		.. render_associated_lists( frame, property_label, titles, has_jcc, has_jccp, game_item_id )
		.. render_card_tiles( frame, cards, size )
		.. render_view_all( frame, property_label, titles, has_more, game_query_name )
		.. '</div>'
end

local function normalize_card_row( row )
	if type( row ) ~= 'table' then
		return nil
	end

	local page_title = trim( row.page_title )
	if page_title == '' then
		return nil
	end

	return {
		item_id = trim( row.item_id ),
		page_title = page_title,
		game_item_id = trim( row.game_item_id ),
		extension_item_id = trim( row.extension_item_id ),
		extension_title = trim( row.extension_title ),
		subextension = trim( row.subextension ),
		release_time = trim( row.release_time ),
		card_number = trim( row.card_number ),
		set_max = trim( row.set_max ),
	}
end

local function run_card_query( property_id, item_id, limit, game_item_id, order_by )
	local reverse_lookup = get_reverse_lookup_library()
	if not reverse_lookup then
		error( 'Bibliothèque mw.pokepediawb indisponible.' )
	end

	local where = {
		{ property = property_id, item = item_id },
	}

	if game_item_id then
		where[#where + 1] = { property = PROP_JEU, item = game_item_id }
	end

	return reverse_lookup.query {
		where = where,
		select = {
			{ kind = 'entity-id', as = 'item_id' },
			{ kind = 'page-title', as = 'page_title' },
			{ property = PROP_JEU, kind = 'item-id', as = 'game_item_id' },
			{ property = PROP_EXTENSION, kind = 'item-id', as = 'extension_item_id' },
			{ property = PROP_EXTENSION, kind = 'item-title', as = 'extension_title' },
			{ property = PROP_SOUS_EXTENSION, kind = 'string', as = 'subextension' },
			{ property = PROP_DATE_SORTIE, kind = 'time', as = 'release_time' },
			{ property = PROP_NUMERO_CARTE, kind = 'string', as = 'card_number' },
			{ property = PROP_NUMERO_MAX_SET, kind = 'string', as = 'set_max' },
		},
		orderBy = order_by or DESC_ORDER,
		limit = limit,
	}
end

local function query_cards_for_item( property_id, item_id, limit, game_item_id, order_by )
	local rows = run_card_query( property_id, item_id, limit, game_item_id, order_by )
	local cards = {}

	for _, row in ipairs( rows ) do
		local card = normalize_card_row( row )
		if card then
			cards[#cards + 1] = card
		end
	end

	return cards
end

local function has_matching_cards( property_id, item_id, game_item_id, order_by )
	local rows = run_card_query( property_id, item_id, 1, game_item_id, order_by )
	return rows[1] ~= nil
end

local function load_cards_for_multiple_items( property_id, item_ids, game_item_id )
	local cards = {}
	local seen_cards = {}

	for _, item_id in ipairs( item_ids ) do
		for _, card in ipairs( query_cards_for_item( property_id, item_id, MAX_MULTI_LOOKUP_LIMIT, game_item_id ) ) do
			if not seen_cards[card.item_id] then
				seen_cards[card.item_id] = true
				cards[#cards + 1] = card
			end
		end
	end

	sort_cards_desc( cards )
	return cards
end

local function trim_to_limit( cards, limit )
	local visible = {}
	local shown = math.min( #cards, limit )

	for index = 1, shown do
		visible[index] = cards[index]
	end

	return visible
end

local function render_property( frame, property_id, property_label, default_limit )
	local args = get_args( frame )
	local titles = get_subject_titles( args )
	local item_ids = get_subject_item_ids( args, titles )
	local limit = get_numeric_arg( args, 'limite' ) or default_limit
	local size = get_numeric_arg( args, 'taille' )
	local game_item_id, game_query_name = normalize_game_filter( args )

	if #item_ids == 0 then
		return render_empty_block()
	end

	local cards = {}
	local has_more = false
	local has_jcc = false
	local has_jccp = false

	if #item_ids == 1 then
		cards = query_cards_for_item( property_id, item_ids[1], limit + 1, game_item_id )
		if #cards == 0 then
			return render_empty_block()
		end

		has_more = #cards > limit
		cards = trim_to_limit( cards, limit )

		if not game_item_id then
			has_jcc = has_matching_cards( property_id, item_ids[1], JCC_ITEM )
			has_jccp = has_matching_cards( property_id, item_ids[1], JCCP_ITEM )
		end
	else
		cards = load_cards_for_multiple_items( property_id, item_ids, game_item_id )
		if #cards == 0 then
			return render_empty_block()
		end

		has_more = #cards > limit
		if not game_item_id then
			has_jcc, has_jccp = classify_games( cards )
		end
		cards = trim_to_limit( cards, limit )
	end

	return render_cards_block(
		frame,
		property_label,
		titles,
		cards,
		size,
		has_more,
		game_item_id,
		game_query_name,
		has_jcc,
		has_jccp
	)
end

function p.principal( frame )
	return render_property( frame, PROP_SUJET_REPRESENTE, 'Sujet représenté', DEFAULT_PRIMARY_LIMIT )
end

function p.secondaire( frame )
	return render_property( frame, PROP_SUJET_SECONDAIRE, 'Sujet secondaire', DEFAULT_SECONDARY_LIMIT )
end

function p.illustrateur( frame )
	return render_property( frame, 'P53', 'Illustrateur de la carte', DEFAULT_ILLUSTRATOR_LIMIT )
end

function p.dresseur( frame )
	return render_property( frame, 'P41', 'Dresseur du Pokémon', DEFAULT_TRAINER_LIMIT )
end

function p.main( frame )
	return p.principal( frame )
end

p._shared = {
	query_cards_for_item = query_cards_for_item,
	has_matching_cards = has_matching_cards,
	trim_to_limit = trim_to_limit,
	render_card_tiles = render_card_tiles,
}

return p