Basculer le menu
Changer de menu des préférences
Basculer le menu personnel
Non connecté(e)
Votre adresse IP sera visible au public si vous faites des modifications.

La documentation pour ce module peut être créée à Module:Case inventaire/doc

local p = {}

-- Internationalization data
local i18n = {
	-- Name formats for pages and files
	filename = 'Invicon $1',
	legacyFilename = 'Icôneinv $1.png',
	modLink = 'Mods/$1/$2',
	
	-- Dependencies
	moduleAliases = [[Module:Case inventaire/Alias]],
	moduleRandom = [[Module:Random]],
	
	-- List of special prefixes which should be handled by
	-- other modules (such as being moved outside links)
	-- When localizing, you might want to use a separate list of patterns
	-- matching the prefixes’ grammatical forms depending on the language
	prefixes = {
		'Tout',
		'Toute',
		'Touts',
		'Toutes',
		'Même',
		'Mêmes',
		any = 'Toute?s?',
		matching = 'Mêmes?',
	},
	-- List of suffixes that are usually stripped from links and tooltips
	suffixes = {
		rev = 'Revision %d+',
		-- berev = 'BE%d+',
		-- jerev= 'JE%d+',
		'endommagé',
		'endommagés',
		'endommagée',
		'endommagées',
		damaged = 'endommagée?s?',
		unwaxed = 
		'non ciré',
		'non cirée',
		be = 'BE',
		lce = 'LCE',
	},
}
p.i18n = i18n

-- Global dependencies and constants
local random = require( i18n.moduleRandom ).random
local aliases = mw.loadData( i18n.moduleAliases )
local pageName = mw.title.getCurrentTitle().text
local Reverselink = require( 'Module:Reverselink' )

-- Auxilliary functions --

-- Splits a given text into fragments separated by semicolons that are not
-- inside square brackets. Originally written by AttemptToCallNil for the
-- Russian wiki.
-- It processes the text byte-by-byte due to being written under a much stricter
-- Lua runtime budget, with no LuaSandbox and mw.text.split being unperformant.
-- See also https://help.fandom.com/wiki/Extension:Scribunto#Known_issues_and_solutions
local function splitOnUnenclosedSemicolons(text)
	local semicolon, lbrace, rbrace = (";[]"):byte(1, 3)
	local nesting = false
	local splitStart = 1
	local frameIndex = 1
	local frames = {}
	
	for index = 1, text:len() do
		local byte = text:byte(index)
		if byte == semicolon and not nesting then
			frames[frameIndex] = text:sub(splitStart, index - 1)
			frameIndex = frameIndex + 1
			splitStart = index + 1
		elseif byte == lbrace then
			assert(not nesting, "Excessive square brackets found")
			nesting = true
		elseif byte == rbrace then
			assert(nesting, "Unbalanced square brackets found")
			nesting = false
		end
	end
	assert(not nesting, "Unbalanced square brackets found")
	frames[frameIndex] = text:sub(splitStart, text:len())
	
	for index = 1, #frames do
		frames[index] = (frames[index]:gsub("^%s+", ""):gsub("%s+$", ""))-- faster than mw.text.trim
	end
	
	return frames
end

-- Performs a simple recursive clone of a table’s values.
-- Probably exists due to mw.clone() being unusable on tables from mw.loadData()
-- at the time (see the link to help.fandom.com above)
local function cloneTable( origTable )
	local newTable = {}
	for k, v in pairs( origTable ) do
		if type( v ) == 'table' then
			v = cloneTable( v )
		end
		newTable[k] = v
	end
	return newTable
end

-- Merges a list, or inserts a string or table into a table,
-- depending on what the second argument happens to be
local function mergeList( parentTable, content )
	local i = #parentTable + 1
	if content[1] then
		-- Merge list into table
		for _, v in ipairs( content ) do
			parentTable[i] = v
			i = i + 1
		end
	else
		-- Add strings or tables to table
		parentTable[i] = content
	end
end

-- Creates the HTML node for a given item.
-- The actual icon file is found and added here
local function makeItem( frame, i, args )
	local item = mw.html.create( 'span' ):addClass( 'invslot-item' )
	if args.imgclass then
		item:addClass( args.imgclass )
	end
	
	if (frame.name or '') == '' then
		-- Empty frame, no icon to add
		return item
	end
	
	-- Frame parameters
	local category
	local title = frame.title or mw.text.trim( args.title or '' )
	local mod = frame.mod
	local name = frame.name or ''
	local num = frame.num
	local description = frame.text
	
	-- Split the extension out of the frame’s name
	local extension
	if name:match('%.gif') or name:match('%.png') then
		extension = name:sub(-4)
		name = name:sub(0, -5)
	elseif name:match('%.webp') then
		extension = '.webp'
		name = name:sub(0, -6)
	else
		extension = '.png'
	end
	
	-- Determine the file name
	local img
	category = ''
	if mod then
		img = i18n.legacyFilename:gsub( '%$1', name .. ' (' .. mod .. ')' )
	elseif name:match( '%.gif$' ) then
		-- Remove file extension from name
		name = name:sub( 0, -5 )
		english_name = Reverselink.xlink_sprites( "Invicon", name )
		if english_name == 'absent' then
			img = 'Grid Unknown.png'
			args.link = "Catégorie:Pages avec des sprites manquants"
			category = "[[Catégorie:Pages avec des sprites manquants]]"
		else
			img = i18n.filename:gsub( '%$1', english_name .. '.gif' )
		end
	elseif name:match( '%.png$' ) then
		-- Remove file extension from name
		name = name:sub( 0, -5 )
		english_name = Reverselink.xlink_sprites( "Invicon", name )
		if english_name == 'absent' then
			img = 'Grid Unknown.png'
			args.link = "Catégorie:Pages avec des sprites manquants"
			category = "[[Catégorie:Pages avec des sprites manquants]]"
		else
			img = i18n.filename:gsub( '%$1', english_name .. '.png' )
		end
	else
		-- Fall back to an individual image if the sprite is lacking
		english_name = Reverselink.xlink_sprites( "Invicon", name )
		if english_name == 'absent' then
			img = 'Grid Unknown.png'
			args.link = "Catégorie:Pages avec des sprites manquants"
			category = "[[Catégorie:Pages avec des sprites manquants]]"
		else
			img = i18n.filename:gsub( '%$1', english_name .. '.png' )
		end
	end
	
	-- Strip suffixes out
	for _, suffix in pairs( i18n.suffixes ) do
		name = name:gsub( ' ' .. suffix .. '$', '' )
	end
	
	-- Determine the link’s target
	local link = args.link or ''
	if link == '' then
		if mod then
			link = i18n.modLink:gsub( '%$1', mod ):gsub( '%$2', name )
		else
			-- Strip the “Damaged” suffix out
			link = name:gsub( '^' .. i18n.suffixes.damaged .. ' ', '' )
		end
	elseif link:lower() == 'none' then
		-- Disable the link
		link = nil
	end
	if link and link:gsub('^%l', string.upper) == pageName then
		link = nil
	end
	
	-- Tooltip titles. If JavaScript is not enabled, the slot will gracefully
	-- degrade to a simplified title without minetip formatting
	local formattedTitle
	local plainTitle
	if title == '' then
		-- If the title is not set, default to the slot’s name
		plainTitle = name
	elseif title:lower() ~= 'none' then
		-- Special character escapes
		plainTitle = title:gsub( '\\\\', '\' ):gsub( '\\&', '&' )
		
		-- The default title will have special formatting code stripped out
		local formatPatterns = {'&[0-9a-jl-qs-vyzr]', '&#%x%x%x%x%x%x', '&$%x%x%x'}
		for _, formatPattern in ipairs( formatPatterns ) do
			if plainTitle:match( formatPattern ) then
				formattedTitle = title
				plainTitle = plainTitle:gsub( formatPattern, '' )
			end
		end
		
		if plainTitle == '' then
			-- If the title field only has formatting code, the frame’s name
			-- is automatically used. For minetips it’s done by JavaScript
			-- by appending the plain title.
			plainTitle = name
		else
			-- Re-encode the 
			plainTitle = plainTitle:gsub( '\', '\\' ):gsub( '&', '&' )
		end
	elseif link then
		-- Disable the tooltip that will otherwise appear with a link
		formattedTitle = ''
	end
	
	-- Minetips are controlled by custom HTML attributes.
	-- See [[MediaWiki:Common.js]] for implementation in JavaScript
	item:attr{
		['data-minetip-title'] = formattedTitle,
		['data-minetip-text'] = description
	}
	
	-- & is re-escaped because mw.html treats attributes
	-- as plain text, but MediaWiki doesn't
	local escapedTitle = ( plainTitle or '' ):gsub( '&', '&' )
	local altText = img .. ' : Image d\'inventaire de l\'objet "' .. name .. '" tel qu\'affiché dans Minecraft'
	if link then
		altText = altText .. ' qui renvoie à l\'article ' .. link
	end
	if formattedTitle or plainTitle or link then
		altText = altText .. ' avec la description : ' .. ( formattedTitle or plainTitle or link )
		if description then
			altText = altText .. ' ' .. description:gsub( '/', ' ' )
		end
		altText = altText:gsub( '&[0-9a-jl-qs-wr]', '' )
	end
	item:addClass( 'invslot-item-image' )
		:wikitext( '[[File:', img, '|32x32px|link=', link or '', '|alt=', altText, '|', escapedTitle, ']]' )
	
	if num and num > 1 and num < 1000 then
		if link then
			item:wikitext( '[[', link, '|' )
		end
		local number = item
			:tag( 'span' )
				:addClass( 'invslot-stacksize' )
				:attr{ title = plainTitle }
				:wikitext( num )
		if args.numstyle then
			number:cssText( args.numstyle )
		end
		if link then
			item:wikitext( ']]' )
		end
	end
	
	item:wikitext( category )
	
	return item
end

-- Main entry point
function p.slot( f )
	local args = f.args or f
	if f == mw.getCurrentFrame() and args[1] == nil then
		args = f:getParent().args
	end
	
	if not args.parsed then
		args[1] = mw.text.trim( args[1] or '' )
	end
	
	local modData = {
		aliases = args.modaliases or '',
		default = args.mod
	}
	if modData.aliases ~= '' then
		modData.aliases = mw.loadData( 'Module:' .. modData.aliases )
	else
		modData.aliases = nil
	end
	if args.mod == '' then
		modData.default = nil
	end
	
	local frames
	if args.parsed then
		frames = args[1]
	elseif args[1] ~= '' then
		local randomise = args.class == 'invslot-large' and 'never' or nil
		frames = p.parseFrameText( args[1], randomise, false, modData )
	end
	local animated = frames and #frames > 1
	local imgClass = args.imgclass
	local numStyle = args.numstyle
	local body = mw.html.create( 'span' ):addClass( 'invslot' ):css{ ['vertical-align'] = args.align }
	if animated then
		body:addClass( 'animated' )
	end
	if args.class then
		body:addClass( args.class )
	end
	if args.style then
		body:cssText( args.style )
	end
	if ( args.default or '' ) ~= '' then
		body:addClass( 'invslot-default-' .. string.ulower( args.default ):gsub( ' ', '-' ) )
	end
	
	--mw.logObject( frames )
	if not frames then
		return tostring( body )
	end
	
	local activeFrame = frames.randomise == true and random( #frames ) or 1
	for i, frame in ipairs( frames ) do
		local item
		-- Table is a list, must contain subframes
		if frame[1] then
			item = body:tag( 'span' ):addClass( 'animated-subframe' )
			local subActiveFrame = frame.randomise == true and random( #frame ) or 1
			for sI, sFrame in ipairs( frame ) do
				local sItem = makeItem( sFrame, sI, args )
				item:node( sItem )
				
				if sI == subActiveFrame then
					sItem:addClass( 'animated-active' )
				end
			end
		else
			item = makeItem( frame, i, args )
			body:node( item )
		end
		if i == activeFrame and animated then
			item:addClass( 'animated-active' )
		end
	end
	
	return tostring( body )
end

--[[Parses the frame text into a table of frames and subframes,
	expanding aliases (and optionally retaining a reference), and
	deciding if the slot can be randomised
--]]
function p.parseFrameText( framesText, randomise, aliasReference, modData )
	local frames = { randomise = randomise }
	local subframes = {}
	local subframe
	local expandedAliases
	local splitFrames = splitOnUnenclosedSemicolons( framesText )
	for i, frameText in ipairs( splitFrames ) do
		frameText = frameText:gsub( '^%s*{%s*', function()
			subframe = true
			return ''
		end )
		if subframe then
			frameText = frameText:gsub( '%s*}%s*$', function()
				subframe = 'last'
				return ''
			end )
		end
		local frame = p.makeFrame( frameText, modData and modData.default )
		local newFrame = frame
		if aliases or modData.aliases then
			local id = frame.name
			if frame.mod then
				id = frame.mod .. ':' .. id
			end
			
			local alias = modData and modData.aliases and modData.aliases[id] or
				aliases and aliases[id]
			if alias then
				newFrame = p.getAlias( alias, frame )
				if aliasReference then
					local curFrame = #frames + 1
					local aliasData = { frame = frame, length = #newFrame }
					if subframe then
						if not subframes.aliasReference then
							subframes.aliasReference = {}
						end
						subframes.aliasReference[#subframes + 1] = aliasData
					else
						if not expandedAliases then
							expandedAliases = {}
						end
						expandedAliases[curFrame] = aliasData
					end
				end
			end
		end
		
		if subframe then
			mergeList( subframes, newFrame )
			-- Randomise starting frame for "Any *" aliases, as long as the alias is the only subframe
			if frames.randomise ~= 'never' and subframes.randomise == nil and
				frame.name:match( '^' .. i18n.prefixes.any .. ' ' ) then
				subframes.randomise = true
			else
				subframes.randomise = false
			end
			if frames.randomise ~= 'never' then
				frames.randomise = false
			end
			if subframe == 'last' then
				-- No point having a subframe containing a single frame,
				-- or the subframe being the only frame
				if #subframes == 1 or #splitFrames == i and #frames == 0 then
					local lastFrame = #frames
					mergeList( frames, subframes )
					
					-- Inherit the randomise flag if it's the only frame
					if #splitFrames == 1 then
						frames.randomise = subframes.randomise
					end
					
					-- Append alias reference data, if present
					if aliasReference and subframes.aliasReference then
						if not expandedAliases then
							expandedAliases = {}
						end
						for i, aliasRefData in pairs(subframes.aliasReference) do
							expandedAliases[lastFrame + i] = aliasRefData
						end
					end
				else
					table.insert( frames, subframes )
				end
				subframes = {}
				subframe = nil
			end
		else
			-- Randomise starting frame for "Any *" aliases, as long as the alias is the only frame
			if frames.randomise ~= 'never' and frame.name:match( '^' .. i18n.prefixes.any .. ' ' ) then
				frames.randomise = true
			else
				frames.randomise = false
			end
			mergeList( frames, newFrame )
		end
	end
	
	frames.aliasReference = expandedAliases
	
	return frames
end

--[[Returns a new table with the parts of the parent frame
	added to the alias
--]]
function p.getAlias( aliasFrames, parentFrame )
	-- If alias is just a name, return the parent frame with the new name
	if type( aliasFrames ) == 'string' then
		local expandedFrame = mw.clone( parentFrame )
		expandedFrame.name = aliasFrames
		return { expandedFrame }
	end
	
	-- Single frame alias, put in list
	if aliasFrames.name then
		aliasFrames = { aliasFrames }
	end
	
	local expandedFrames = {}
	for i, aliasFrame in ipairs( aliasFrames ) do
		local expandedFrame
		if type( aliasFrame ) == 'string' then
			expandedFrame = { name = aliasFrame }
		else
			expandedFrame = cloneTable( aliasFrame )
		end
		expandedFrame.title = parentFrame.title or expandedFrame.title
		expandedFrame.mod = parentFrame.mod or expandedFrame.mod
		expandedFrame.num = parentFrame.num or expandedFrame.num
		expandedFrame.text = parentFrame.text or expandedFrame.text
		
		expandedFrames[i] = expandedFrame
	end
	
	return expandedFrames
end

function p.expandAlias( parentFrame, alias )
	return p.getAlias( alias, parentFrame )
end

function p.stringifyFrame( frame )
	if not frame.name then
		return ''
	end
	return string.format(
		'[%s]%s:%s,%s[%s]',
		frame.title or '',
		frame.mod or 'Minecraft',
		frame.name,
		frame.num or '',
		frame.text or ''
	)
end

function p.stringifyFrames( frames )
	for i, frame in ipairs( frames ) do
		frames[i] = p.stringifyFrame( frame )
	end
	return table.concat( frames, ';' )
end

-- Splits up the frame text into its parts
function p.makeFrame( frameText, mod )
	-- Simple frame with no parts
	if not frameText:match( '[%[:,]' ) then
		return {
			mod = mod,
			name = mw.text.trim( frameText ),
		}
	end
	
	frameText = frameText:gsub( '%s*([%[%]:,;])%s*', '%1' )
	
	local frame = {}
	frame.title = frameText:match( '^%[([^%]]+)%]' )
	
	frame.mod = frameText:match( '([^:%]]+):' ) or mod
	local vanilla = { v = 1, vanilla = 1, mc = 1, minecraft = 1 }
	if frame.mod and vanilla[mw.ustring.lower( frame.mod )] or frame.mod == '' then
		frame.mod = nil
	end
	
	local nameStart = ( frameText:find( ':' ) or frameText:find( '%]' ) or 0 ) + 1
	if nameStart - 1 == #frameText then
		nameStart = 1
	end
	frame.name = frameText:sub( nameStart, ( frameText:find( '[,%[]', nameStart ) or 0 ) - 1 )
	
	frame.num = math.floor( frameText:match( ',(%d+)' ) or 0 )
	if frame.num == 0 then
		frame.num = nil
	end
	
	frame.text = frameText:match( '%[([^%]]+)%]$' )
	
	return frame
end
function p.getParts( frameText, mod )
	return p.makeFrame( frameText, mod )
end
 
return p