Module:Utils: Difference between revisions

From Eco - English Wiki
[unchecked revision][pending revision]
Demian (talk | contribs)
m Add tableHasData, tableHasAtMostOneItem functions. Optimize getSortedValues, getSortedKeys functions to not attempt to sort tables with 0 or 1 items.
Tag: Reverted
No edit summary
 
(7 intermediate revisions by 3 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
--- Trims and parses the args into a table, then returns the table
function p.normalise(args)
for k, v in pairs(args) do
v = mw.text.trim(tostring(v))
if v ~= '' then
args[k] = v
end
end
return args
end


--- Trims and parses the args into a table, then returns the table
--- Trims and parses the args into a table, then returns the table
Line 28: Line 41:
return icon
return icon
else
else
return 'NoImage.png'
return 'NoIcon.png'
end
end
end
function p.itemId(name)
return name:gsub('%s+', '') .. 'Item'
end
end


Line 44: Line 61:
end
end
return set[item] ~= nil
return set[item] ~= nil
end
--- Build HTML code for an icon image.
-- @param name string
-- @param size string|nil One of: <code>"iconNormal"</code> (64px) or <code>"iconRecipe"</code> (44px). Default: <code>"iconNormal"</code>
-- @param bg string|nil
-- @param border string|nil
-- @param too_expensive boolean|nil
-- @author User:Avaren
function p.build_icon(name, link, size, bg, border, too_expensive)
local L = require('Module:Localization') -- local import
if not size then
size = 'iconNormal'
end
local icon_bg
if bg then
icon_bg = bg
end
local icon_border
if border then
icon_border = border
end
local item_data = mw.loadData('Module:ItemData')
local item = item_data.items[name]
local image
if item then
if item['group'] == L.t('Skill Books') then
image = 'SkillBook.png'
icon_bg = 'iconGold'
elseif item['group'] == L.t('Skill Scrolls') then
image = 'Skill Scroll'
icon_bg = 'iconGold'
-- Attempt to generate skill page
elseif in_array(L.t('Basic Research'), item['tagGroups']) then
image = string.sub(item['untranslated'], 1, -7):gsub('%s+', '') .. '_Icon.png'
icon_bg = 'paperBasic'
elseif in_array(L.t('Advanced Research'), item['tagGroups']) then
image = string.sub(item['untranslated'], 1, -10):gsub('%s+', '') .. '_Icon.png'
icon_bg = 'paperAdvanced'
elseif in_array(L.t('Modern Research'), item['tagGroups']) then
image = string.sub(item['untranslated'], 1, -8):gsub('%s+', '') .. '_Icon.png'
icon_bg = 'paperModern'
else
image = p.checkImage(item['untranslated'], too_expensive)
end
if not icon_bg then
if item['group'] == L.t('Food') then
icon_bg = 'iconGreen'
elseif item['carried'] == L.t('Hands') then
icon_bg = 'iconBrown'
end
end
else
image = p.checkImage(name, too_expensive)
end
if not icon_bg then
icon_bg = 'iconBlue'
end
if border then
icon_border = border
else
icon_border = 'borderBlue'
end
if size == 'iconNormal' then
icon_container = 'iconContainer'
else
icon_container = 'iconContainerSmall'
end
if not link then
link = ''
else
link = '|link='..link
end
local file = '[[File:' .. image .. '|frameless|class=' .. size .. ' ' .. icon_bg .. link ..']]'
return '<div class="' .. icon_container .. '"><div class="iconStack">' .. file .. '</div><div class="iconBorder ' .. icon_border .. '" style="position:absolute;"></div></div>'
end
--- Get HTML code for an icon image.
-- @author User:Avaren
function p.Icon(frame)
args = p.normaliseArgs(frame)
return p.build_icon(args.name, args.link, args.size, args.bg, args.border, args.too_expensive)
end
end


Line 237: Line 165:
--- Get all keys from <code>tbl</code> and sort them in alphabetical order.
--- Get all keys from <code>tbl</code> and sort them in alphabetical order.
-- @param #table tbl Table to get keys from
-- @param #table tbl Table to get keys from
-- @return #table Input table keys in alphabetical order, or <code>tbl</code> exactly if it has 0 or 1 items.
-- @return #table Input table keys in alphabetical order.
-- @author User:Demian
-- @author User:Demian
-- @see getSortedValues
-- @see getSortedValues
function p.getSortedKeys(tbl)
function p.getSortedKeys(tbl)
-- Check if there is anything to even sort.
local sorted = {}
if p.tableHasAtMostOneItem(tbl) then
return tbl
else
local sorted = {}


for key, _ in pairs(tbl) do
for key in pairs(tbl) do
table.insert(sorted, key)
table.insert(sorted, key)
end
end


table.sort(sorted)
table.sort(sorted)


return sorted
return sorted
end
end
end


--- Get all values from <code>tbl</code> and sort them in alphabetical order.
--- Get all values from <code>tbl</code> and sort them in alphabetical order.
-- @param #table tbl Table to get values from
-- @param #table tbl Table to get values from
-- @return #table Input table values in alphabetical order, or <code>tbl</code> exactly if it has 0 or 1 items.
-- @return #table Input table values in alphabetical order.
-- @author User:Demian
-- @author User:Demian
-- @see getSortedKeys
-- @see getSortedKeys
function p.getSortedValues(tbl)
function p.getSortedValues(tbl)
-- Check if there is anything to even sort.
local sorted = {}
if p.tableHasAtMostOneItem(tbl) then
return tbl
else
local sorted = {}


for _, value in ipairs(tbl) do
for _, value in ipairs(tbl) do
table.insert(sorted, value)
table.insert(sorted, value)
end
end


table.sort(sorted)
table.sort(sorted)


return sorted
return sorted
end
end
end


Line 418: Line 336:
end
end


--- Try to iterate a table to see if it has more than 0 items.
function p.mapColour(colourName)
--
    local foreground = "white"
-- Will at most iterate over the first item.
    local background = "#1165AF"
-- <code>mw.LoadData</code> prevents <code>#tbl</code> from working correctly.
-- @param #table tbl Table check
-- @return #bool <code>true</code> if table has at least 1 item.
-- @author User:Demian
function p.tableHasData(tbl)
for _, _ in ipairs(tbl) do
return true
end


return false
    mw.log(colourName)
end


--- Try to iterate a table to see if it has less than 2 items.
    colour_map = {
--
        ["aliceblue"] = "f0f8ff",
-- Will at most iterate over the first two items.
        ["antiquewhite"] = "faebd7",
-- <code>mw.LoadData</code> prevents <code>#tbl</code> from working correctly.
        ["aqua"] = "00ffff",
-- @param #table tbl Table check
        ["aquamarine"] = "7fffd4",
-- @return #bool <code>true</code> if table has 0 or 1 items, otherwise <code>false</code>.
        ["azure"] = "f0ffff",
-- @author User:Demian
        ["beige"] = "f5f5dc",
function p.tableHasAtMostOneItem(tbl)
        ["bisque"] = "ffe4c4",
local count = 0
        ["black"] = "000000",
        ["blanchedalmond"] = "ffebcd",
        ["blue"] = "0000ff",
        ["blueviolet"] = "8a2be2",
        ["brown"] = "a52a2a",
        ["burlywood"] = "deb887",
        ["cadetblue"] = "5f9ea0",
        ["chartreuse"] = "7fff00",
        ["chocolate"] = "d2691e",
        ["coral"] = "ff7f50",
        ["cornflowerblue"] = "6495ed",
        ["cornsilk"] = "fff8dc",
        ["crimson"] = "dc143c",
        ["cyan"] = "00ffff",
        ["darkblue"] = "00008b",
        ["darkcyan"] = "008b8b",
        ["darkgoldenrod"] = "b8860b",
        ["darkgray"] = "a9a9a9",
        ["darkgreen"] = "006400",
        ["darkkhaki"] = "bdb76b",
        ["darkmagenta"] = "8b008b",
        ["darkolivegreen"] = "556b2f",
        ["darkorange"] = "ff8c00",
        ["darkorchid"] = "9932cc",
        ["darkred"] = "8b0000",
        ["darksalmon"] = "e9967a",
        ["darkseagreen"] = "8fbc8f",
        ["darkslateblue"] = "483d8b",
        ["darkslategray"] = "2f4f4f",
        ["darkturquoise"] = "00ced1",
        ["darkviolet"] = "9400d3",
        ["deeppink"] = "ff1493",
        ["deepskyblue"] = "00bfff",
        ["dimgray"] = "696969",
        ["dodgerblue"] = "1e90ff",
        ["feldspar"] = "d19275",
        ["firebrick"] = "b22222",
        ["floralwhite"] = "fffaf0",
        ["forestgreen"] = "228b22",
        ["fuchsia"] = "ff00ff",
        ["gainsboro"] = "dcdcdc",
        ["ghostwhite"] = "f8f8ff",
        ["gold"] = "ffd700",
        ["goldenrod"] = "daa520",
        ["gray"] = "808080",
        ["green"] = "008000",
        ["greenyellow"] = "adff2f",
        ["honeydew"] = "f0fff0",
        ["hotpink"] = "ff69b4",
        ["indianred "] = "cd5c5c",
        ["indigo "] = "4b0082",
        ["ivory"] = "fffff0",
        ["khaki"] = "f0e68c",
        ["lavender"] = "e6e6fa",
        ["lavenderblush"] = "fff0f5",
        ["lawngreen"] = "7cfc00",
        ["lemonchiffon"] = "fffacd",
        ["lightblue"] = "add8e6",
        ["lightcoral"] = "f08080",
        ["lightcyan"] = "e0ffff",
        ["lightgoldenrodyellow"] = "fafad2",
        ["lightgrey"] = "d3d3d3",
        ["lightgreen"] = "90ee90",
        ["lightpink"] = "ffb6c1",
        ["lightsalmon"] = "ffa07a",
        ["lightseagreen"] = "20b2aa",
        ["lightskyblue"] = "87cefa",
        ["lightslateblue"] = "8470ff",
        ["lightslategray"] = "778899",
        ["lightsteelblue"] = "b0c4de",
        ["lightyellow"] = "ffffe0",
        ["lime"] = "00ff00",
        ["limegreen"] = "32cd32",
        ["linen"] = "faf0e6",
        ["magenta"] = "ff00ff",
        ["maroon"] = "800000",
        ["mediumaquamarine"] = "66cdaa",
        ["mediumblue"] = "0000cd",
        ["mediumorchid"] = "ba55d3",
        ["mediumpurple"] = "9370d8",
        ["mediumseagreen"] = "3cb371",
        ["mediumslateblue"] = "7b68ee",
        ["mediumspringgreen"] = "00fa9a",
        ["mediumturquoise"] = "48d1cc",
        ["mediumvioletred"] = "c71585",
        ["midnightblue"] = "191970",
        ["mintcream"] = "f5fffa",
        ["mistyrose"] = "ffe4e1",
        ["moccasin"] = "ffe4b5",
        ["navajowhite"] = "ffdead",
        ["navy"] = "000080",
        ["oldlace"] = "fdf5e6",
        ["olive"] = "808000",
        ["olivedrab"] = "6b8e23",
        ["orange"] = "ffa500",
        ["orangered"] = "ff4500",
        ["orchid"] = "da70d6",
        ["palegoldenrod"] = "eee8aa",
        ["palegreen"] = "98fb98",
        ["paleturquoise"] = "afeeee",
        ["palevioletred"] = "d87093",
        ["papayawhip"] = "ffefd5",
        ["peachpuff"] = "ffdab9",
        ["peru"] = "cd853f",
        ["pink"] = "ffc0cb",
        ["plum"] = "dda0dd",
        ["powderblue"] = "b0e0e6",
        ["purple"] = "800080",
        ["red"] = "ff0000",
        ["rosybrown"] = "bc8f8f",
        ["royalblue"] = "4169e1",
        ["saddlebrown"] = "8b4513",
        ["salmon"] = "fa8072",
        ["sandybrown"] = "f4a460",
        ["seagreen"] = "2e8b57",
        ["seashell"] = "fff5ee",
        ["sienna"] = "a0522d",
        ["silver"] = "c0c0c0",
        ["skyblue"] = "87ceeb",
        ["slateblue"] = "6a5acd",
        ["slategray"] = "708090",
        ["snow"] = "fffafa",
        ["springgreen"] = "00ff7f",
        ["steelblue"] = "4682b4",
        ["tan"] = "d2b48c",
        ["teal"] = "008080",
        ["thistle"] = "d8bfd8",
        ["tomato"] = "ff6347",
        ["turquoise"] = "40e0d0",
        ["violet"] = "ee82ee",
        ["violetred"] = "d02090",
        ["wheat"] = "f5deb3",
        ["white"] = "ffffff",
        ["whitesmoke"] = "f5f5f5",
        ["yellow"] = "ffff00",
        ["yellowgreen"] = "9acd32"
    }


for _, value in ipairs(tbl) do
    colour_found = colour_map[colourName:lower()]
count = count + 1
    if colour_found ~= nil then
 
        background = '#' .. colour_found
if 1 < count then
    end
return false
end
end


return 2 > count
    return foreground, background
end
end


return p
return p

Latest revision as of 10:16, 5 September 2024

This module provides utility functions used from other modules.

Usage[edit source]

Add the following line of code at the top of your file.

local Utils = require("Module:Utils")

-- You may then call functions from this module in your script. For example:
local tableLength = Utils.tableLen(myTable)

local p = {}

--- Trims and parses the args into a table, then returns the table
function p.normalise(args)

	for k, v in pairs(args) do
		v = mw.text.trim(tostring(v))
		if v ~= '' then
			args[k] = v
		end
	end

	return args
end

--- Trims and parses the args into a table, then returns the table
-- @author User:Avaren
function p.normaliseArgs(frame)
	local origArgs = frame:getParent().args
	local args = {}

	for k, v in pairs(origArgs) do
		v = mw.text.trim(tostring(v))
		if v ~= '' then
			args[k] = v
		end
	end

	return args
end

--- Get path to icon file.
-- @author User:Avaren
function p.checkImage(name, too_expensive)
	local icon = name:gsub('%s+', '') .. '_Icon.png'
	if too_expensive then
		return icon
	end

	if mw.title.makeTitle('File', icon).file.exists then
		return icon
	else
		return 'NoIcon.png'
	end
end

function p.itemId(name)
	return name:gsub('%s+', '') .. 'Item'
end

--- Check if <code>item</code> is in given <code>array</code>.
-- @param item Item to look for
-- @param #table array Table to check
-- @return #bool <code>true</code> if <code>item</code> is in <code>array</code>
-- @author User:Avaren
local function in_array(item, array)
	-- Should only use on short arrays
	local set = {}
	for _, l in ipairs(array) do
		set[l] = true
	end
	return set[item] ~= nil
end

--- Calculate the length of a table by iterating over every item in it.
--
-- <code>mw.LoadData</code> prevents <code>#tbl</code> from working correctly.
-- @param #table tbl Table to calculate the length of
-- @return #number Length of the table.
-- @author User:Avaren
function p.tableLen(tbl)
	local count = 0
	for _, v in ipairs(tbl) do
		if v == nil then
			return count
		end
		count = count + 1
	end
	return count
end

--- Check if <code>value</code> is not <code>nil</code> and return it or if it is <code>nil</code> fall back to <code>default</code>.
-- @param value Value to check
-- @param default Value to fall back to
-- @return <code>value</code> if it is not <code>nil</code>
-- @return <code>default</code> if <code>value</code> is <code>nil</code>
-- @author User:Demian
-- @see valueOrDash
-- @see formatNilToYesNo
-- @see formatBoolToYesNo
function p.valueOrDefault(value, default)
	return nil == value and default or value
end

--- Check if <code>value</code> is not <code>nil</code> and return it or if it is <code>nil</code> fall back to the em-dash (—).
--
-- The em-dash (—) is commonly used represent a missing, not applicable (N/A), or a negative ("no") value with just a single character.
-- @param value Value to check
-- @return <code>value</code> if it is not <code>nil</code>
-- @return #string "—" if <code>value</code> is <code>nil</code>
-- @author User:Demian
-- @see valueOrDefault
function p.valueOrDash(value)
	return nil == value and "—" or value
end

--- Check if <code>value</code> is not <code>nil</code> and return "Yes" or "No".
-- @param value Value to check
-- @return #string "Yes" if <code>value</code> is not <code>nil</code>
-- @return #string "No" if <code>value</code> is <code>nil</code>
-- @author User:Demian
-- @see valueOrDefault
-- @usage formatNilToYesNo("Hello") == "Yes"
-- @usage formatNilToYesNo(nil) == "No"
function p.formatNilToYesNo(value)
	-- TODO: Support i18n.
	return nil == value and "No" or "Yes"
end

--- Check if <code>value</code> <em>evaluates</em> as <code>true</code> and return "Yes" or "No".
-- @param value Value to evaluate. Does not have to be a bool.
-- @return #string "Yes" if <code>value</code> evaluates as <code>true</code>
-- @return #string "No" if <code>value</code> evaluates as <code>false</code>
-- @author User:Demian
-- @see valueOrDefault
-- @usage formatBoolToYesNo("") == true
-- @usage formatBoolToYesNo(123) == true
-- @usage formatBoolToYesNo(nil) == false
function p.formatBoolToYesNo(value)
	-- TODO: Support i18n.
	return value and "Yes" or "No"
end

--- Format the input values into a string representing the range between the values.
--
-- Returning an an empty string intended to ease concatenation with other strings.
-- The en-dash (–) (instead of the hyphen-minus "-") is the appropriate character to signify a range of values.
-- @param #number min Minimum value (left side)
-- @param #number max Maximum value (right side)
-- @param #number default Default value in case of an error (only value).
-- @param #string valueFormat Format string used with <code>mw.ustring.format</code>.
-- @return #string "<code>min</code>–<code>max</code>" if <code>min < max</code>
-- @return #string "<code>default</code>" formatted with <code>valueFormat</code> if <code>min == max</code> or <code>min > max</code> and <code>default ~= nil</code>
-- @return #string "" (empty string) if either <code>min</code> <strong>or</strong> <code>max</code> do not convert to a numerical value
-- @return #nil <code>nil</code> if <code>min == max</code> or <code>min > max</code> and <code>default == nil</code>
-- @author User:Demian
function p.toRangeString(min, max, default, valueFormat)
	min = tonumber(p.valueOrDefault(min, nil))
	max = tonumber(p.valueOrDefault(max, nil))
	default = tonumber(p.valueOrDefault(default, nil))

	if nil ~= min and nil ~= max then
		if min < max then
			return mw.ustring.format(mw.ustring.format("%s–%s", valueFormat, valueFormat), min, max)
		elseif nil == default then
			return nil
		else
			return mw.ustring.format(valueFormat, default)
		end
	end

	return ""
end

--- Get all keys from <code>tbl</code> and sort them in alphabetical order.
-- @param #table tbl Table to get keys from
-- @return #table Input table keys in alphabetical order.
-- @author User:Demian
-- @see getSortedValues
function p.getSortedKeys(tbl)
	local sorted = {}

	for key in pairs(tbl) do
		table.insert(sorted, key)
	end

	table.sort(sorted)

	return sorted
end

--- Get all values from <code>tbl</code> and sort them in alphabetical order.
-- @param #table tbl Table to get values from
-- @return #table Input table values in alphabetical order.
-- @author User:Demian
-- @see getSortedKeys
function p.getSortedValues(tbl)
	local sorted = {}

	for _, value in ipairs(tbl) do
		table.insert(sorted, value)
	end

	table.sort(sorted)

	return sorted
end

--- Split <code>str</code> by the given character.
-- @param #string str String to split
-- @param #string separator String that separates values in <code>str</code>. May optionally be surrounded by 1 <em>whitespace</em> character by default.
-- @return #table Table of strings that were split from <code>str</code>.
-- @author User:Demian
-- @usage splitString("hello, world", ",") == {"hello", "world"}
function p.splitString(str, separator)
	local tbl = {}

	for token in mw.ustring.gmatch(str, mw.ustring.format("%%s?([^%s]+)%%s?", separator)) do
		table.insert(tbl, token)
	end

	return tbl
end

--- Sort items in the given list of values <code>str</code> separated with <code>separator</code> and return them as a single string.
-- @param #string str String with values separated by <code>separator</code>
-- @param #string separator String that separates values in <code>str</code>
-- @param #string joiner String used to join sorted values from <code>str</code>.
-- @return #string <code>str</code> with items sorted in alphabetical order.
-- @author User:Demian
-- @usage sortListString("Dog,Ape, Cat", ",", ";") == "Ape;Cat;Dog"
function p.sortListString(str, separator, joiner)
	-- Split string by commas.
	-- Sort items.
	-- Rejoin into string.
	return table.concat(p.getSortedValues(p.splitString(str, separator)), joiner)
end

--- Check if a page with the title "<code>name</code> (<code>disambiguationTitle</code>)" exists in the database and return that page title, otherwise return "<code>name</code>".
--
-- Use sparingly as this uses a comparatively slow MediaWiki function p.to check if a page exists.
--
-- Using this function p.will create a new entry in the <code>Special:WantedPages</code> list.
-- Be careful when calling this function p.and do not pass garbage into its parameters so you do not clog up that list.
-- This is a long-standing issue with MediaWiki that has not yet been solved, and may not be possible to solve without an architectural change to the software.
-- @param #string name Name of a page.
-- @param #string disambiguationTitle Disambiguation clarifier in a page title.
-- @return #string "<code>name</code> (<code>disambiguationTitle</code>)"
-- @return #string "<code>name</code>"
-- @author User:Demian
function p.getDirectPageName(name, disambiguationTitle)
	-- Try to get the actual end page instead of the disambiguation page if it exists.
	-- E.g. Salmon has "Salmon (animal)" and "Salmon (item)" as well as the "Salmon" disambiguation page between these two.
	local directPage = mw.ustring.format("%s (%s)", name, disambiguationTitle)
	return mw.title.new(directPage).exists and directPage or name
end

--- Create a wikilink with [[square brackets]] from parameters.
-- @param #string pageName The actual name of a page to create a link to
-- @param #string displayText Text to display as a clickable link instead of the page name. If <code>nil</code>, <code>pageName</code> is displayed instead.
-- @param #bool twoLineDisplayText Force the <em>last word</em> of <code>displayText</code> on the next line
-- @return #string "[[<code>name</code>|<code>displayText</code>]]" if <code>displayText</code> is not <code>nil</code>
-- @return #string "[[<code>name</code>]]" if <code>displayText</code> is <code>nil</code> or the same string as <code>name</code>
-- @author User:Demian
function p.formatWikilink(pageName, displayText, twoLineDisplayText)
	local finalDisplayText = p.valueOrDefault(displayText, pageName)

	if twoLineDisplayText then
		local lastSpaceIdx = mw.ustring.find(finalDisplayText, " [^ ]*$")

		if nil ~= lastSpaceIdx then
			finalDisplayText = mw.ustring.format("%s<br>%s", mw.ustring.sub(finalDisplayText, 0, lastSpaceIdx-1), mw.ustring.sub(finalDisplayText, lastSpaceIdx+1))
		end
	end

	if pageName == finalDisplayText then
		return mw.ustring.format("[[%s]]", pageName)
	else
		return mw.ustring.format("[[%s|%s]]", pageName, finalDisplayText)
	end
end

--- Add thousands separator to given number and use custom decimal point.
--
-- Extension:NumberFormat is more extensive, but more cumbersome to use and also not installed at the moment.
-- @param #string number Number to format. Is processed as a string regardless of type.
-- @param #string thousandsSeparator String to place between each set of 3 digits. Default: " "
-- @param #string decimalPoint String to place between the whole and fractional part of the number. Default: "."
-- @return #string <code>number</code> with the specified thousands separator and decimal point.
-- @return #string <code>number</code> unchanged if it contained 1 or more characters that are <strong>not</strong> a: digit, one of ".,-", a space.
-- @author User:Demian
-- @usage formatNumber(-1234567.89) = "-1 234 567.89"
-- @usage formatNumber("1234567,89", ".", "_") = "1,234,567_89"
function p.formatNumber(number, thousandsSeparator, decimalPoint)
	-- Default separator to space.
	if nil == thousandsSeparator then
		thousandsSeparator = " "
	end

	-- Default point to period.
	if nil == decimalPoint then
		decimalPoint = "."
	end

	-- We're dealing with formatting a string here.
	local numberString = tostring(number)

	-- Check if the input number is reasonable.
	-- Does NOT check for multiple instance of each character.
	-- E.g. Inputting something like 123-456.789 will lead to incorrect results.
	-- I can't handle every edge case: garbage in, garbage out.
	-- The user has to have some responsibility in inputting reasonable numbers.
	if mw.ustring.find(numberString, "[^%d%.%-, ]") then
		return number
	end

	-- Split input into parts.
	-- 1st group: MAY start with a "-".
	-- 2nd group: MUST contain 1 or more digits-
	-- 3rd group: MAY start with with one of ".,"
	-- 3rd group: MAY have 0 or more digits.
	local _, _, minus, digits, fraction = mw.ustring.find(tostring(numberString), "(-?)(%d+)([%.,]?%d*)")

	-- Reverse the string of digits.
	-- Append the thousands separator after (before when reversed again) each set of 3 digits.
	digits = mw.ustring.gsub(string.reverse(digits), "(%d%d%d)", mw.ustring.format("%%1%s", thousandsSeparator))

	-- Replace the existing decimal separator with the specified one.
	if "" ~= fraction then
		fraction = mw.ustring.format("%s%s",decimalPoint, mw.ustring.sub(fraction, 2))
	end

	-- Reverse the string of digits back to the original direction.
	-- If the string digits starts with the thousands separator, remove the separator.
	-- Add the optional minus in front and the optional fractional part at the back.
	-- Need to remember to escape the thousandsSeparator, it could be "." which would translate to "any character"!
	return mw.ustring.format("%s%s%s", minus, mw.ustring.gsub(string.reverse(digits), mw.ustring.format("^%%s", thousandsSeparator), ""), fraction)
end

--- Check if the string is empty or <code>nil</code>.
-- @param #string str String to check
-- @return #bool <code>true</code> if <code>str</code> is <code>nil</code> or an empty string ("").
-- @author User:Demian
function p.isEmpty(str)
	return nil == str or "" == str
end

function p.mapColour(colourName)
    local foreground = "white"
    local background = "#1165AF"

    mw.log(colourName)

    colour_map = {
        ["aliceblue"] = "f0f8ff",
        ["antiquewhite"] = "faebd7",
        ["aqua"] = "00ffff",
        ["aquamarine"] = "7fffd4",
        ["azure"] = "f0ffff",
        ["beige"] = "f5f5dc",
        ["bisque"] = "ffe4c4",
        ["black"] = "000000",
        ["blanchedalmond"] = "ffebcd",
        ["blue"] = "0000ff",
        ["blueviolet"] = "8a2be2",
        ["brown"] = "a52a2a",
        ["burlywood"] = "deb887",
        ["cadetblue"] = "5f9ea0",
        ["chartreuse"] = "7fff00",
        ["chocolate"] = "d2691e",
        ["coral"] = "ff7f50",
        ["cornflowerblue"] = "6495ed",
        ["cornsilk"] = "fff8dc",
        ["crimson"] = "dc143c",
        ["cyan"] = "00ffff",
        ["darkblue"] = "00008b",
        ["darkcyan"] = "008b8b",
        ["darkgoldenrod"] = "b8860b",
        ["darkgray"] = "a9a9a9",
        ["darkgreen"] = "006400",
        ["darkkhaki"] = "bdb76b",
        ["darkmagenta"] = "8b008b",
        ["darkolivegreen"] = "556b2f",
        ["darkorange"] = "ff8c00",
        ["darkorchid"] = "9932cc",
        ["darkred"] = "8b0000",
        ["darksalmon"] = "e9967a",
        ["darkseagreen"] = "8fbc8f",
        ["darkslateblue"] = "483d8b",
        ["darkslategray"] = "2f4f4f",
        ["darkturquoise"] = "00ced1",
        ["darkviolet"] = "9400d3",
        ["deeppink"] = "ff1493",
        ["deepskyblue"] = "00bfff",
        ["dimgray"] = "696969",
        ["dodgerblue"] = "1e90ff",
        ["feldspar"] = "d19275",
        ["firebrick"] = "b22222",
        ["floralwhite"] = "fffaf0",
        ["forestgreen"] = "228b22",
        ["fuchsia"] = "ff00ff",
        ["gainsboro"] = "dcdcdc",
        ["ghostwhite"] = "f8f8ff",
        ["gold"] = "ffd700",
        ["goldenrod"] = "daa520",
        ["gray"] = "808080",
        ["green"] = "008000",
        ["greenyellow"] = "adff2f",
        ["honeydew"] = "f0fff0",
        ["hotpink"] = "ff69b4",
        ["indianred "] = "cd5c5c",
        ["indigo "] = "4b0082",
        ["ivory"] = "fffff0",
        ["khaki"] = "f0e68c",
        ["lavender"] = "e6e6fa",
        ["lavenderblush"] = "fff0f5",
        ["lawngreen"] = "7cfc00",
        ["lemonchiffon"] = "fffacd",
        ["lightblue"] = "add8e6",
        ["lightcoral"] = "f08080",
        ["lightcyan"] = "e0ffff",
        ["lightgoldenrodyellow"] = "fafad2",
        ["lightgrey"] = "d3d3d3",
        ["lightgreen"] = "90ee90",
        ["lightpink"] = "ffb6c1",
        ["lightsalmon"] = "ffa07a",
        ["lightseagreen"] = "20b2aa",
        ["lightskyblue"] = "87cefa",
        ["lightslateblue"] = "8470ff",
        ["lightslategray"] = "778899",
        ["lightsteelblue"] = "b0c4de",
        ["lightyellow"] = "ffffe0",
        ["lime"] = "00ff00",
        ["limegreen"] = "32cd32",
        ["linen"] = "faf0e6",
        ["magenta"] = "ff00ff",
        ["maroon"] = "800000",
        ["mediumaquamarine"] = "66cdaa",
        ["mediumblue"] = "0000cd",
        ["mediumorchid"] = "ba55d3",
        ["mediumpurple"] = "9370d8",
        ["mediumseagreen"] = "3cb371",
        ["mediumslateblue"] = "7b68ee",
        ["mediumspringgreen"] = "00fa9a",
        ["mediumturquoise"] = "48d1cc",
        ["mediumvioletred"] = "c71585",
        ["midnightblue"] = "191970",
        ["mintcream"] = "f5fffa",
        ["mistyrose"] = "ffe4e1",
        ["moccasin"] = "ffe4b5",
        ["navajowhite"] = "ffdead",
        ["navy"] = "000080",
        ["oldlace"] = "fdf5e6",
        ["olive"] = "808000",
        ["olivedrab"] = "6b8e23",
        ["orange"] = "ffa500",
        ["orangered"] = "ff4500",
        ["orchid"] = "da70d6",
        ["palegoldenrod"] = "eee8aa",
        ["palegreen"] = "98fb98",
        ["paleturquoise"] = "afeeee",
        ["palevioletred"] = "d87093",
        ["papayawhip"] = "ffefd5",
        ["peachpuff"] = "ffdab9",
        ["peru"] = "cd853f",
        ["pink"] = "ffc0cb",
        ["plum"] = "dda0dd",
        ["powderblue"] = "b0e0e6",
        ["purple"] = "800080",
        ["red"] = "ff0000",
        ["rosybrown"] = "bc8f8f",
        ["royalblue"] = "4169e1",
        ["saddlebrown"] = "8b4513",
        ["salmon"] = "fa8072",
        ["sandybrown"] = "f4a460",
        ["seagreen"] = "2e8b57",
        ["seashell"] = "fff5ee",
        ["sienna"] = "a0522d",
        ["silver"] = "c0c0c0",
        ["skyblue"] = "87ceeb",
        ["slateblue"] = "6a5acd",
        ["slategray"] = "708090",
        ["snow"] = "fffafa",
        ["springgreen"] = "00ff7f",
        ["steelblue"] = "4682b4",
        ["tan"] = "d2b48c",
        ["teal"] = "008080",
        ["thistle"] = "d8bfd8",
        ["tomato"] = "ff6347",
        ["turquoise"] = "40e0d0",
        ["violet"] = "ee82ee",
        ["violetred"] = "d02090",
        ["wheat"] = "f5deb3",
        ["white"] = "ffffff",
        ["whitesmoke"] = "f5f5f5",
        ["yellow"] = "ffff00",
        ["yellowgreen"] = "9acd32"
    }

    colour_found = colour_map[colourName:lower()]
    if colour_found ~= nil then
        background = '#' .. colour_found
    end

    return foreground, background
end

return p