Module:MilAward
| This module is rated as beta. It is considered ready for widespread use, but as it is still relatively new, it should be applied with some caution to ensure results are as expected. |
| Editing of this module by new or unregistered users is currently disabled. See the protection policy and protection log for more details. If you cannot edit this module and you wish to make a change, you can submit an edit request, discuss changes on the talk page, request unprotection, log in, or create an account. |
Usage
Various templates use this data. Start with {{MilAward}} and make sure to check out the talk page for some stats and tests.
Data structure
This module uses the structure:
code = {Code = "xx", Description = "xx", Class="", RibbonImage="Ribbon - Question mark.png", PageLink="Wikipedia:WikiProject Orders, decorations, and medals", PostNom="", Country="None", Note="", Org="None", RecipCat="" },
- xx : the UNIQUE code we use to access this record.
- Description : Description to be used as Alt Description, so it will be used when producing the link
- class: intended to be for the class of the award, it's turning out to be useful for other things as well
- RibbonImage : File name of the file for the ribbon to be displayed
- PageLink : Name of Wiki Article which the specific award will link to
- PostNom : If this award has a Postnominal attached to it, enter that here or leave blank
- Country. : This should be obvious, but I haven't made a decision whether to use codes or country name
- org: Organisation
- RecipCat: Contents of this field are used as a category name. The idea is to add the person who's medal you've added to their page, to the category of recipients of that medal, automagically
Examples:
- Ribbon :

- Description:
Military Merit Medal (MMM)
With different sizes:
- Ribbon :

- Description:
Military Merit Medal (MMM)
What this does is it provides the data to various routines which combine it in different ways. For example, Ribbon will display just the image, but will link it to the link page. Desc displays the ribbon, then adds the description next to it. When a template calls one of the routines in the module, the {{{size}}} is replaced with the second un-named parameter when the template is called. That is passed on to this module to set the size of the image. The "PageLink" provides the name of an article on Wikipedia with no wikilinking. (In theory it could be a URL, but that's not preferred) The contents of the Description field can be any text which can be used as the "alt text" for an image link. Please be careful with this. Using links or curly braces "{}" or inverted commas '' etc can break that entry as well as, potentially, the rest of the entries in the template.
How this works, is that the first unnamed parameter when you call the template is used as the value to look up the corresponding value in this list. Once that value has been found, then the fields in that line are available to the module. These fields are combined in different ways to produce the specific formatting required for the template that called this module. For this reason, it's very important that this format is not broken or interfered with!
There are now named parameters.
- code: The code for the medal
- size: Size of the image
Still to be implemented
- Country=yes - Add the display of country to the medal description. WIll have to figure out if I display just what's in the data or do an ISO lookup for a country name.
- Class=no - Allow the turning off of the addition of what's in the "Class" field to the medal description
- TextSize=xx - Allow the specification of the size of the text. Probably a percentage.
- Org=yes - Allow the turning on of the display of the contents of the "Org" field
- Notes=no - Allow the turning off of the display of the contents of the "Notes" field
A note on countries
All the data is in one large lookup table rather than splitting it into files for each country. It just means it's easier to maintain and it's less likely to get busybodies interfering and rearranging things because the "Good Idea Fairy" told them it would be a good idea.
Editors should try to keep this as organised as possible. Search the file for your country's name. If there isn't any mention of it, then create a heading using a comment for your country and add any relevant entries there. Within the country heading, not a bad idea to create sub-headings for different things. Examples might be "Military/Civilian", "Arms of service", "Types of awards". This then can be duplicated on the Documentation page of this Module and will make more sense to new readers and ease of use for maintainers and editors.
The data in this module is self-documenting. Place the template {{tlx|MilAward Stats}] in your sandbox or any other page and you'll see what I mean.
require('strict')
local p = {}
local data = mw.loadData('Module:MilAward/data')
local getArgs = require('Module:Arguments').getArgs
local function countByCountry()
-- Counts how many award entries exist per country.
local StatCount = {}
for code, record in pairs(data) do
local country = record.Country
StatCount[country] = (StatCount[country] or 0) + 1
end
return StatCount
end
function p.GraphData(frame)
-- Produces the x= and y= lines for a graph of number of entries per country.
local StatCount = countByCountry()
local xValues, yValues = {}, {}
for country, count in pairs(StatCount) do
table.insert(xValues, country)
table.insert(yValues, count)
end
local output = " \n| x=" .. table.concat(xValues, ", ") .. " \n| y=" .. table.concat(yValues, ", ") .. "\n"
return output
end
function p.Stats(frame)
local StatCount = countByCountry()
-- Stable alphabetical order so the country table is readable before the reader sorts it.
local countries = {}
for country, _ in pairs(StatCount) do
table.insert(countries, country)
end
table.sort(countries)
local wikiRows = {}
for _, country in ipairs(countries) do
table.insert(wikiRows, "|-\n|" .. country .. "||" .. StatCount[country])
end
-- Do not append GraphData-style | x= / | y= here: those were leftover from the old
-- Graph:Chart workflow and rendered as broken text after the wikitable.
local wikiCode = "{| class=\"wikitable sortable col2right\"\n|-\n! Country !! Count\n" .. table.concat(wikiRows, "\n") .. "\n|}"
local tablist = p.PrintStats()
local output = wikiCode .. "\n\n" .. tablist
return output
end
function p.PrintStats()
-- Convert key-value pairs into a list of {code, data[code]} pairs
local pairsList = {}
for code, values in pairs(data) do
table.insert(pairsList, {code, values})
end
-- Sort the list based on the "Description" field of each pair (guard against a missing field)
table.sort(pairsList, function(a, b)
return (a[2].Description or "") < (b[2].Description or "")
end)
-- Generate wiki table code for the sorted list
local tableCode = "{| class=\"wikitable sortable\"\n"
tableCode = tableCode .. "|-\n"
tableCode = tableCode .. "! Code !! Old Code !! Description !! Country !! Postnom !! Ribbon !! Org\n"
for i, pair in ipairs(pairsList) do
local code, values = pair[1], pair[2]
local oldcode = values.Code or ""
local country = values.Country or ""
local description = values.Description or ""
local class = values.Class or ""
local postnom = values.PostNom or ""
local ribbonimage = values.RibbonImage or ""
local pagelink = values.PageLink or ""
local note = values.Note or ""
local org = values.Org or ""
tableCode = tableCode .. "|-\n"
tableCode = tableCode .. "|" .. code .. "\n"
if code == oldcode then
tableCode = tableCode .. "| \n"
else
tableCode = tableCode .. "|" .. oldcode .. "\n"
end
tableCode = tableCode .. "|[[" .. pagelink .. "|" .. description
if string.len(class) > 0 then
tableCode = tableCode .. "]] '''(" .. class .. ")'''"
else
tableCode = tableCode .. "]]"
end
if string.len(note) > 0 then
tableCode = tableCode .. mw.getCurrentFrame():expandTemplate{ title = 'Efn', args = {note} } -- Note: Page needs {{notelist}}
end
tableCode = tableCode .. "\n"
tableCode = tableCode .. "|" .. country .. "\n"
tableCode = tableCode .. "|" .. postnom .. "\n"
tableCode = tableCode .. "|[[File:" .. ribbonimage .. "|x30px]]\n"
tableCode = tableCode .. "|" .. org .. "\n"
end
tableCode = tableCode .. "|}"
return tableCode
end
function p.Display(frame)
-- Take parameters out of the frame and pass them to p._Display(). Return the result.
local templateArgs = getArgs(frame)
local code = templateArgs[1] or 'None'
local size = templateArgs[2] or "40px"
local namedcode = templateArgs["code"]
if namedcode then
code = namedcode
end
local namedsize = templateArgs["size"]
if namedsize then
size = namedsize
end
return p._Display(code, size)
end
function p._Display(code, size)
-- Debug/inspection helper: dumps the raw data fields for a code. (size is currently unused.)
local rec = data[code]
if not rec then
return '<span style="color:#d33"> (Code not found: <b>' .. tostring(code) .. '</b>)</span> [[Category:MilAward Error]]'
end
local output = ""
output = output .. " '''Found award with code''': " .. rec.Code .. "<br />"
output = output .. "''Code'': " .. rec.Code .. "<br />"
output = output .. "''Description'': " .. rec.Description .. "<br />"
output = output .. "''Ribbon Image'': " .. rec.RibbonImage .. "<br />"
output = output .. "''Post-Nominal'': " .. rec.PostNom .. "<br />"
output = output .. "''Page Link'': " .. rec.PageLink .. "<br />"
output = output .. "''Country'': " .. rec.Country .. "<br />"
return output
end
local function _Ribbon(code, size)
local rec = data[code]
if string.len(size) < 1 then
size = "x12px" -- Check that there is a value for size and if not, set the value
end
local ribbonimage = rec.RibbonImage or ""
if string.len(ribbonimage) < 1 then -- If there is no ribbonimage defined for the record, then assign a default one.
ribbonimage = "NOT-AVAILABLE-RIBBON.svg"
end
local output = "[[File:" .. ribbonimage .. "|".. size .. "|link=" .. rec.PageLink .. "|" .. rec.Description .. " ''" .. rec.PostNom .. " ''" .. "]]"
return output
end
function p.Ribbon(frame)
-- Function extracts the parameters from the frame (passed into the function)
-- It checks that the first argument is not empty, and returns an error if so
local templateArgs = getArgs(frame)
local code = templateArgs[1] or 'None'
local size = templateArgs[2] or "x12px"
local namedcode = templateArgs["code"] -- Check for named argument "code" and overwrite the value in the local variable if it has a value
if namedcode then
code = namedcode
end
local namedsize = templateArgs["size"] -- Check for named argument "size" and overwrite the value in the local variable if it has a value
if namedsize then
size = namedsize
end
if string.match(code, "%*") then -- The code field has asterisks in it. Raise an error
local output = '<span style="color:#d33"> Bad Code: <b>[' .. code .. ']</b>. Replace all * with x</span> [[Category:MilAward Error]]'
return output
end -- End if
if not code or '' == code then
return '<span style="color:#d33"> (Invalid Code: <b>' .. code .. '</b> )</span> [[Category:MilAward Error]]'
end
-- Code is case sensitive - do not uppercase it
if not data[code] then
return '<span style="color:#d33"> (Code not found: <b>' .. code .. '</b> )</span> [[Category:MilAward Error]]'
end
return _Ribbon (code, size)
end
local function _RibbonDesc(code, size, country, norib, nocat, org)
-- This routine is where the data is structured for output
local rec = data[code]
local output = ''
if norib ~= "yes" then -- They do NOT want the ribbon to display if this is yes
output = output .. _Ribbon(code, size) -- Start by getting the ribbon. Call the function designed to do that
end
output = output .. " [[" .. rec.PageLink .. "|" .. rec.Description .. "]] " -- add the pagelink and description
if rec.Class and string.len(rec.Class) > 0 then -- Only add the Class if there is something in the field
output = output .. " (''" .. rec.Class .. "'') "
end
if string.len(rec.PostNom or "") > 0 then -- Only add the postnom if there is something in the field
output = output .. " ('''" .. rec.PostNom .. "''') "
end
if string.len(rec.Note or "") > 0 then -- Only add the Note if there is something in the field
output = output .. mw.getCurrentFrame():expandTemplate{ title = 'Efn', args = {rec.Note} } -- Note: Page needs {{notelist}}
end
if string.len(country) > 0 then -- if a value for country has been specified
if country:upper() ~= (rec.Country or ""):upper() then -- check that the country specified does NOT equal that on record
output = output .. " (" .. rec.Country .. ") " -- add the country code to the end of the output
end
end
if string.len(org) > 0 then -- if a value for org has been specified
if org:upper() == "YES" then -- check that it is YES
if string.len(rec.Org or "") > 0 then -- Check there is actually something in the org field
output = output .. " (''" .. rec.Org .. "'') " -- add the Org code to the end of the output
end
end
end
local nocat_length = string.len(nocat)
if nocat_length > 1 then -- If ANYTHING has been specified as a value for nocat, then it's true
-- Caller opted out of recipient categorisation
else
-- Need to exclude adding the recipient category if it is not in mainspace.
local current_title = mw.title.getCurrentTitle()
if current_title.namespace == 0 then -- mainspace only
local recipcat = rec.RecipCat or "" -- If there is a recipient category specified, then add it to the output
local categories = {}
if string.len(recipcat) > 0 then
local categoryTitle = mw.title.new('Category:' .. mw.text.trim(recipcat))
if categoryTitle.exists and not categoryTitle.isRedirect then
-- category exists and is not a redirect
categories[#categories+1] = " [[Category:" .. recipcat .. "]]"
end
-- else: category does not exist or is a redirect - not added
end
output = output .. table.concat(categories)
end
end
return output
end
function p.RibbonDesc(frame)
local templateArgs = getArgs(frame)
local code = 'Unknown' -- initialise a local variable called code to something rational
local size = 'x20px' -- initialise a local variable called size to something rational
if templateArgs[1] then code = templateArgs[1] end -- check that it is not "nil" before assigning the value
if templateArgs[2] then size = templateArgs[2] end -- Check that it is not "nil" before assigning the value
local namedcode = templateArgs["code"] -- assign the local variable "namedcode" to the value of the argument called code
if namedcode then -- if the assignment of value has worked ie there was actually a parameter called code with a value then
code = namedcode -- assign the value found above to the variable "code" overwriting any other value it may have had
end
local namedsize = templateArgs["size"] -- exactly the same as for "code" above
if namedsize then
size = namedsize
end
local country = templateArgs["country"] or '' -- if the argument "country" has been passed, assign it to a local variable
local norib = templateArgs["norib"] or ''
if not code or '' == code then -- if there was no code assigned (field empty) then return an error of invalid code
local output = '<span style="color:#d33"> (Invalid Code: <b>' .. code .. '</b>)</span>'
return output
end
if string.match(code, "%*") then -- The code field has asterisks in it. Raise an error
local output = '<span style="color:#d33"> Bad Code: <b>[' .. code .. ']</b>. Replace all * with x</span> [[Category:MilAward Error]]'
return output
end -- End if
-- Do not convert to uppercase. Codes are case sensitive
if not data[code] then
return '<span style="color:#d33"> (Code: <b>' .. code .. '</b> Not found)</span>'
end
local nocat = templateArgs["nocat"] or '' -- Assign local value for nocat parameter
local org = templateArgs["org"] or '' -- Assign local value for org parameter
return _RibbonDesc (code, size, country, norib, nocat, org)
end
function p.Stack(frame)
local templateArgs = getArgs(frame)
local size = templateArgs["size"] or "x12px"
local RibbonList = {}
for i = 1, 40 do -- 40 is the max number of params that will be examined.
local rl = templateArgs[i] or ""
if rl ~= "" then
table.insert(RibbonList, rl)
end
end
local row = {}
for i, rl in ipairs(RibbonList) do
if data[rl] then -- check whether this is found in the data
table.insert(row, _Ribbon(rl, size))
end
end
return table.concat(row, " ")
end
function p.DescList(frame)
local templateArgs = getArgs(frame)
local size = templateArgs["size"] or 'x20px'
local namedcountry = templateArgs["country"] or 'yes'
local output = '\n'
for i = 1, 40 do -- 40 is the max number of positional params examined, matching p.Stack
local value = templateArgs[i]
if value and string.len(value) > 1 and data[value] then
output = output .. "* " .. _RibbonDesc(value, size, namedcountry, '', '', '') .. "\n"
end
end
return output
end
function p.PostNom(frame)
local templateArgs = getArgs(frame)
local size = templateArgs["size"] or '85%'
local sep = templateArgs["sep"] or ', '
if sep == "none" then
sep = " "
end
local output = '<span class="noexcerpt nowraplinks" style="font-size:' .. size .. '; font-weight:normal;">'
local any = false
for i = 1, 40 do -- positional args only, so this both preserves order and skips size/sep automatically
local value = templateArgs[i]
if value and string.len(value) > 1 and data[value] then
if string.len(data[value].PostNom or "") > 1 then
output = output .. "[[" .. data[value].PageLink .. "|" .. data[value].PostNom .. "]]" .. sep
any = true
end
end
end
if any then
local seplen = (string.len(sep) + 1) * -1 -- Take length of sep, add one to it and make it negative (* -1)
output = output:sub(1, seplen) -- Remove the trailing separator
end
output = output .. '</span>'
return output
end
-- Initial code by John Dovey (13 April 2023) [[User:BoonDock]],
-- Updated with bug fixes 7 September 2026
-- Updated 10 September 2026: Stats no longer dumps GraphData | x=/| y= after the country wikitable; country rows sorted A–Z
-- - PostNom no longer corrupts its <span> tag when nothing matches
-- - DescList/PostNom iterate args positionally (like Stack already did) so award order matches what the editor typed, and named params can't collide with codes
-- - PrintStats header is back on one line (was silently misaligning the table)
-- - _Display guards against unknown codes instead of throwing a script error
-- - Leading-comma bug in the GraphData/Stats CSV lines fixed, and the duplicated country-counting loop is now one shared countByCountry() helper
-- - _Ribbon/_RibbonDesc cache data[code] once instead of re-indexing repeatedly, and guard Country/PostNom/Note/Org/RibbonImage against missing fields (same nil-crash class as the Description sort bug)
-- - Mainspace check now uses current_title.namespace == 0 instead of the namespace-name-length heuristic
-- - Dead code removed: unreachable if not size check, stray commented-out lines, unused module-level GlobalTemplateArgs, redundant local wikiCode shadow
-- x --
return p