Module:Liste des cartes d'un sujet
Apparence
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( '%[', '[' )
escaped = escaped:gsub( '%]', ']' )
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 :</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