Module:Item
This module provides the functionality of the {{item}} template.
Usage
Wikitext
The module can be called from wikitext with the functions listed below.
go
{{#invoke:Item| go | <string of parameters> }}
Displays the entity. This function expects its parameters as one long string in the format @param1:value^@param2:value^. It is about 20% faster overall to do this compared to using individual parameters, and performance is an important consideration for this widely used module.
This function is generally only intended to be called from the template mentioned above. See the template documentation for details about the available parameters.
purge
{{#invoke:Item| purge | <string of parameters> }}
When the go function is invoked, its result is cached to improve the performance of subsequent invocations with identical parameters. This function purges the cache, which may be necessary in case there are issues.
Take the invocation {{#invoke:Item|go|@name:Stone Block^@text:Stone^}} (via {{item|Stone Block|Stone}}) as an example. The go function computes the output for this set of parameters and stores that output to the cache. When this very same invocation occurs again later on the same page, the go function does not have to compute the output fully anew again, because it already did so once before. It can simply retrieve the output from the cache.
The cache is cleared automatically after 24 hours but it can be cleared earlier by invoking the purge function. This may be necessary if a cached output is erroneous.
This function is generally only intended to be called from the {{item/purge}} template. Simply take the normal {{item}} transclusion that should be purged (e.g. {{item|Stone Block|Stone}}), change item to item/purge (e.g. {{item/purge|Stone Block|Stone}}), and save it on a sandbox page (just previewing is not sufficient). Make sure to include any relevant transclusions of {{options}} that affect the {{item}}.
storeImageInfo
{{#invoke:Item| storeImageInfo | <file name> }}
Stores image info to the Imageinfo cargo table.
Other modules
The module can be called from another module with the functions listed below.
parse
require('Module:Item').parse('<string of parameters>')
Transforms the combined parameter string into a table of parameters.
| Code | Variable result
|
|---|---|
local item = require('Module:Item')
local result = item.parse('@name:Stone Block^@text:Stone^@image:Ebonstone Block.png^@note:Corrupt^')
|
{
[1] = "Stone Block",
[2] = "Stone",
["image"] = "Ebonstone Block.png",
["name"] = "Stone Block",
["note"] = "Corrupt",
["text"] = "Stone",
}
|
-- Import functions
local vardefine = mw.ext.VariablesLua.vardefine
local trim = mw.text.trim
local cargo = mw.ext.cargo
local cache = mw.ext.LuaCache
local eicons = require('Module:Exclusive').simpleEicons
local tr = require('Module:Tr')
---Holds the tables with the l10n information for the different languages, taken from the l10n submodule.
local l10nInfo = mw.loadData('Module:Item/l10n')
---Holds the l10n information for the current language, as key-value pairs.
local l10nTable
---The current language. Determines which l10n table to use.
local lang
---Holds the arguments from the template call.
local args
local shouldCache = true
local options, optionsSnap = (function()
local options, snap = {}, ''
local op = require('Module:Options')
for _,key in ipairs(op.getAllKeys('item')) do
local v = op.get('item', key)
snap = snap..v..'∥'
if v ~= '' then
options[key] = v
end
end
return options, snap
end)()
-- Note:
-- for getArg('x', f()), f() will always be parsed;
-- for getArg('x') or f(), f() will only be parsed when used.
local function getArg(key, defaultForEmpty, defaultForBlank)
local value = args[key]
if not value then -- there is no |key=xxx
return defaultForEmpty
elseif value == '' then -- explicit |key=|
if defaultForBlank == nil then
return defaultForEmpty
else
return defaultForBlank
end
else
return value
end
end
local md5 = function(str)
return mw.hash.hashValue('md5', str)
end
local function pairsByKeys(t)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, function(x, y) return tostring(x) < tostring(y) end)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end
---@param value string
---@param default bool
---@return bool
local function bool(value, default)
if value then
value = string.lower(tostring(value))
if value == 'y' or value == 'yes' or value == '1' or value == 'true' or value == 'on' then
return true
elseif value == 'n' or value == 'no' or value == '0' or value == 'false' or value == 'off' then
return false
end
return default
end
end
---Return the l10n string associated with the `key`.
---@param key string
---@return string
local function l10n(key)
return l10nTable[key] or l10nInfo['en'][key]
end
local function getCacheKey(arg, lang)
local arr = { lang, optionsSnap }
for k,v in pairsByKeys(arg) do
arr[#arr+1] = k..':'..v
end
return table.concat(arr, '※');
end
---Split the `str` on each `div` in it and return the result as a table. It can be over 60x faster then mw.text.split
---Original version credit: http://richard.warburton.it. This version trims each substring.
---@param div string
---@param str string
---@return table|boolean
local function explode(div,str)
if (div=='') then return false end
local pos,arr = 0,{}
-- for each divider found
for st,sp in function() return string.find(str,div,pos,true) end do
arr[#arr + 1] = trim(string.sub(str,pos,st-1)) -- Attach chars left of current divider
pos = sp + 1 -- Jump past current divider
end
arr[#arr + 1] = trim(string.sub(str,pos)) -- Attach chars right of last divider
return arr
end
---Encode a string for use in a MediaWiki URI fragment.
---@param anchorid string
---@return string
local function anchorencode(anchorid)
-- mw.uri.anchorEncode turns spaces into the HTML entity for underscores but
-- we don't want that
-- (e.g. `mw.uri.anchorEncode('hello world')` => 'hello_world')
-- (the parentheses are there to drop the second return value of `string.gsub`)
return (string.gsub(mw.uri.anchorEncode(anchorid), '_', '_'))
end
---Extract scale, width, and height from an input string. Up to two of the three can be empty in the input.
---Example: `5x7px*0.75` → `0.75`, `5`, `7`
---@param size string
---@return string basescale
---@return number width
---@return number height
local function parseSize(size)
if not size then return end
local basescale, width, height
size, basescale = unpack(explode('*', size))
if size ~= '' then
width, height = unpack(explode('x', string.gsub(size, 'px', '')))
width, height = tonumber(width), tonumber(height)
if width == 0 then width = nil end
if height == 0 then height = nil end
end
return basescale, width, height
end
---Return width, height, and caching date for the specified `imagename` from the Imageinfo cargo table.
---@param imagename string
---@return number width
---@return number height
---@return string cached
local function getInfoFromCargo(imagename)
-- try to get from cargo cache
local result = mw.ext.cargo.query('Imageinfo', 'width, height, cached', {
-- md5name for case-sensitive query.
where = 'md5name='.. "'"..md5(imagename).."'",
orderBy = "cached DESC",
limit = 1,
})
for _, row in ipairs(result) do
return tonumber(row['width']), tonumber(row['height']), row['cached']
end
end
---Store width and height of the specified `imagename` to the Imageinfo cargo table and return them.
---Width and height are computed via the `#imgw:` and `#imgh:` parser functions, respectively.
---@param imagename string
---@return number width
---@return number height
local function storeInfoToCargo(imagename)
-- don't cache {{item}}'s result when parsing imagesize fails.
shouldCache = false
local imageTitle = mw.title.new("File:" .. imagename)
local width, height = imageTitle.file.width, imageTitle.file.height
if width and width ~= 0 and height and height ~= 0 then
shouldCache = true -- ok, cache it.
mw.getCurrentFrame():callParserFunction('#cargo_store:_table=Imageinfo',{
image = imagename,
md5name = md5(imagename),
width = width,
height = height,
cached = os.time(),
})
end
return width, height
end
---Retrieve the dimensions of the specified `image` from the Imageinfo cargo table.
---If it doesn't have any data for the image yet, store it.
---@param imagename string
---@return number width
---@return number height
local function getSizeInfo(imagename)
local width, height, cached = getInfoFromCargo(imagename)
-- cache missed, init cache
if not cached then
width, height = storeInfoToCargo(imagename)
end
if width == 0 then width = nil end
if height == 0 then height = nil end
return width, height
end
---Compute the final width and height of the image.
---If necessary, retrieve data from or store data to the Imageinfo cargo table.
---@param imagename string
---@param width number
---@param height number
---@param scale number
---@param maxwidth number
---@param maxheight number
---@return number width
---@return number height
local function getImageSize(imagename, width, height, scale, maxwidth, maxheight)
-- get size info from image file itself (may be expensive)
local w, h = getSizeInfo(imagename) -- store data to cache
-- if width and height are not given as input, but scale/maxwidth/maxheight are, then
-- set width and height to the original dimensions of the image
if not width and not height and (scale or maxwidth or maxheight) then
width, height = w, h
end
-- apply scale to width/height if needed
if scale then
if width then width = width * scale end
if height then height = height * scale end
end
-- apply maxwidth/maxheight
if maxwidth then
if width then
if width > maxwidth then width = maxwidth end
else
if height then width = maxwidth end
end
end
if maxheight then
if height then
if height > maxheight then height = maxheight end
else
if width then height = maxheight end
end
end
-- round to natural numbers
if width then width = math.ceil(width) end
if height then height = math.ceil(height) end
return width, height
end
---Extract width and height from an input string.
---Example: `6x9px` → `6`, `9`
---@param maxsize string
---@return number maxwidth
---@return number maxheight
local function parseMaxSize(maxsize)
if not maxsize then return end
local maxwidth, maxheight = unpack(explode('x', string.gsub(maxsize, 'px', '')))
maxwidth, maxheight = tonumber(maxwidth), tonumber(maxheight)
if maxwidth == 0 then maxwidth = nil end
if maxheight == 0 then maxheight = nil end
return maxwidth, maxheight
end
---Assemble the final wikicode for an image.
---@param imagename string
---@param link string
---@param text string
---@param size string As accepted by the `[[File:` syntax, e.g. `5x7px*0.75`.
---@param scale number This will be multiplied by the scale in `size`, if necessary.
---@param maxsize string
---@return string
local function imagecode(imagename, link, text, size, scale, maxsize)
local imageOutput = '[[File:' .. imagename .. '|link='.. link .. '|' .. text
if size or scale or maxsize then
local basescale, width, height = parseSize(size) -- width, height: number or nil (basescale is string!)
scale = (tonumber(scale) or 1) * (tonumber(basescale) or 1) -- combine the scale parameter and scale from the size parameter
if scale == 0 or scale == 1 then
scale = nil
end
local maxwidth, maxheight = parseMaxSize(maxsize)
width, height = getImageSize(imagename, width, height, scale, maxwidth, maxheight) -- can be 0
if width or height then
imageOutput = imageOutput .. '|' .. (width or '') .. 'x' .. (height or '') .. 'px'
end
end
return imageOutput .. ']]'
end
---Return the full `[[File:` wikicode for each image in the input (multiple are separated with `/`).
---@param image string
---@param link string
---@param text string
---@param size string
---@param scale string
---@param maxsize string
---@return string
local function images(image, link, text, size, scale, maxsize)
if not image:find('/') then
-- there is only one image in the input
return imagecode(image, link, text, size, scale, maxsize)
end
-- there are multiple images in the input, separated with a slash
image = explode('/', image)
local result = ''
if size and size:find('/') then
-- there are multiple sizes in the size parameter
size = explode('/', size) -- so turn it into a table
for i, v in ipairs(image) do -- iterate over the images
result = result .. imagecode(v, link, text, size[i], scale, maxsize) -- create the wikicode (using the respective size)
end
else
for i, v in ipairs(image) do -- iterate over the images
result = result .. imagecode(v, link, text, size, scale, maxsize) -- create the wikicode
end
end
return result
end
---Return a string like `Internal Item ID: `, depending on the `_type`.
---@param _type '"item"'|'"tile"'|'"wall"'|'"npc"'|'"mount"'|'"buff"'|'"projectile"'|'"armor"'
---@return string
local function getIdText(_type)
local id_text
if _type == 'item' then -- a shortcut for faster
id_text = l10n('id_text_item')
elseif _type == 'tile' then
id_text = l10n('id_text_tile')
elseif _type == 'wall' then
id_text = l10n('id_text_wall')
elseif _type == 'npc' then
id_text = l10n('id_text_npc')
elseif _type == 'mount' then
id_text = l10n('id_text_mount')
elseif _type == 'buff' or _type == 'debuff' then
id_text = l10n('id_text_buff')
elseif _type == 'projectile' then
id_text = l10n('id_text_projectile')
elseif _type == 'armor' then
id_text = l10n('id_text_armor')
else
id_text = l10n('id_text_item')
end
return id_text
end
-----------------------------------------------------------------
-- main return object
return {
go = function(frame, inputArgs)
args = inputArgs or frame:getParent().args
lang = getArg('lang') or require('Module:Lang').get() or 'en'
-- cache?
local cache_key = getCacheKey(args, lang)
local cached = cache.get(cache_key)
if cached then
return cached
end
l10nTable = l10nInfo[lang] or l10nInfo['en']
local name = trim(getArg(1, ''))
if name == '' then
name = frame:expandTemplate{ title = (getArg('type') or options.type or 'item')..'NameFromId', args = {getArg('id'), lang='en'} }
end
local trName = (lang == 'en') and name or tr.translate(name, lang)
local text = getArg('t')
if not text then
local t = trim(getArg(2, ''))
if t == '' then
text = trName
else
text = frame:expandTemplate{ title = 'displaytext', args = {name, t, lang=lang} }
end
end
local nolink = bool(getArg('nolink') or options.nolink)
local link = nolink and '' or getArg('link', nil, '') or tr.translateLink(name, lang) -- now: link == '' means nolink
-- set output flags
local outputImage, outputText, outputTable = true, true, false
local mode = getArg('mode', nil, '') or options.mode
if mode then
if mode == 'image' or mode == 'imageonly' or mode =='onlyimage' then
outputText = false
elseif mode == 'text' or mode == 'noimage' then
outputImage = false
elseif mode == 'table' or mode == '2-cell' then
outputTable = true
end
end
local hovertext
if outputImage and not outputText then
-- with image only, the hovertext will only be displayed on the image, so it should be text or {{tr|name}} or link (in that order)
if text ~= '' then
hovertext = text
elseif name ~= '' then
hovertext = trName
else
hovertext = link
end
else
-- with image and/or text, the hovertext will be displayed on the image and on the text, so it should be {{tr|name}} or text or link (in that order)
if name ~= '' then
hovertext = trName
elseif text ~= '' then
hovertext = text
else
hovertext = link
end
end
local class = 'i'
local imageOutput, textOutput
-- wikicode for the image(s)
if outputImage then
local imageArg = getArg('image') or string.gsub(name, "[:/]%s*", " ")..'.'..getArg('ext', 'png')
if string.find(imageArg, '%[%[[fF]ile:') then
imageOutput = '<span class="img">' .. imageArg .. '</span>'
else
imageOutput = images(imageArg, link, hovertext, getArg('size'), getArg('scale') or options.scale, getArg('maxsize') or options.maxsize)
end
else
imageOutput = ''
end
-- wikicode for the text
if outputText then
local note, note2, bignote = getArg('note'), getArg('note2'), getArg('bignote')
local id = getArg('id')
local showid = bool(getArg('showid') or options.showid or (id and 'y'))
-- prepare: wrap?
local wrap
if showid or note2 then
wrap = false
else
wrap = bool(getArg('wrap') or options.wrap)
end
-- prepare: eicons
local iconstr = ''
if text ~= '' then -- no display text no eicons.
local icons = getArg('icons') or options.icons or 'y'
icons = bool(icons, icons)
if icons then
local small = bool((showid or note2 or wrap) and 'y' or getArg('small') or options.small)
if icons == true then
iconstr = eicons(name, lang, small)
else
-- make the size of {{eicons}} from {{{icon}}} input match {{{small}}} setting.
if small then
iconstr = string.gsub(icons, ' class="eico ', ' class="eico s ')
else
iconstr = string.gsub(icons, ' class="eico s ', ' class="eico ')
end
end
end
end
-- prepare: link and display text
if link == '' or text == '' or string.find(text, '%[%[.-%]%]') then
text = '<span title="'..hovertext..'">'..text..'</span>'
else
if text == link then
text = '<span>[['..text..']]</span>'
else
text = '<span>[['..link..'|'..text..']]</span>'
end
end
-- assemble HTML code
local content = text -- item name link text first.
-- '-w' class means 'wrapmode', optimized for multiple lines of text. But it should be disabled for single line text.
local wrapclass = false
if wrap then
-- eicons in the same line
if iconstr ~= '' then
wrapclass = true
content = content .. iconstr
end
-- note in a new line
if note then
wrapclass = true
content = content .. '<span class="note">' .. note .. '</span>'
end
else
-- note in the same line
if note then
content = content .. '<span class="note">' .. note .. '</span>'
end
-- eicons in the same line
if iconstr ~= '' then
content = content .. iconstr
end
-- note2 in a new line
if note2 then
wrapclass = true
content = content .. '<span class="note2">' .. note2 .. '</span>'
end
-- id in a new line
if showid then
wrapclass = true
local idType = (getArg('type') or options.type or 'item'):lower()
if not id then
-- get ID automatically via {{itemIdFromName}} or the like
id = frame:expandTemplate{ title = idType .. 'IdFromName', args = {name} }
end
local idText = getIdText(idType)
content = content .. '<span class="id">' .. idText .. id .. '</span>'
end
end
if wrapclass then
class = class .. ' -w'
end
if bignote then
textOutput = '<span>' .. content .. '</span><span>' .. bignote .. '</span>'
else
textOutput = '<span>' .. content .. '</span>'
end
else
textOutput = ''
end
-- handle custom CSS
local inputClass, inputCss = getArg('class', nil, '') or options.class, getArg('css', nil, '') or options.css
if inputClass then
class = class .. ' ' .. inputClass -- add to existing classes
end
local attr = {class = class}
if inputCss then
attr.style = inputCss -- set the style attribute to parameter value
end
-- anchor:
local anchor = bool(getArg('anchor') or options.anchor)
if anchor then
anchor = mw.text.tag('s', {class = 'anchor', id = anchorencode(name)}, '')
if lang ~= 'en' then
anchor = mw.text.tag('s', {class = 'anchor', id = anchorencode(trName)}, '')
end
end
-- output
local return_string
if outputTable then
-- table output
attr.class = class
local rowspan = getArg('rowspan')
local rowspan_text = (rowspan and (' rowspan=' .. rowspan) or '')
-- prepare the two cells
local first_cell_pre = rowspan_text .. ' class="il1c"'
local first_cell_content = mw.text.tag('span', attr, imageOutput)
if anchor then
first_cell_content = anchor .. first_cell_content
end
local second_cell_pre = rowspan_text .. ' class="il2c"'
local second_cell_content = mw.text.tag('span', attr, textOutput)
-- combine
return_string = first_cell_pre .. " | " .. first_cell_content .. " || " .. second_cell_pre .. " | " .. second_cell_content
else
-- non-table output (text/image)
return_string = mw.text.tag('span', attr, anchor and (imageOutput .. textOutput .. anchor) or (imageOutput .. textOutput) )
end
-- cache output for reuse
if shouldCache then
cache.set(cache_key, return_string, 3600*24) -- cache for 24 hours
end
-- output
return return_string
end,
purge = function(frame, inputArgs)
args = inputArgs or frame:getParent().args
lang = getArg('lang') or require('Module:Lang').get() or 'en'
local cache_key = getCacheKey(args, lang)
cache.delete(cache_key)
end,
storeImageInfo = function(frame)
local width, height = storeInfoToCargo(frame.args[1])
if not width or width == 0 or not height or height == 0 then
return
else
vardefine('__imageinfo:exists', '1')
vardefine('__imageinfo:width', width)
vardefine('__imageinfo:height', height)
return
end
end,
}