Permanently protected module
From Wikipedia, the free encyclopedia


require('strict')



local p = {}

local lang = mw.language.getContentLanguage();									-- language object for this wiki

local presentation ={};															-- table of tables that contain currency presentation data

local properties;





--[[--------------------------< I S _ S E T >------------------------------------------------------------------



Whether variable is set or not.  A variable is set when it is not nil and not empty.



]]



local function is_set( var )

	return not (var == nil or var == '');

end





--[[--------------------------< M A K E _ S H O R T _ F O R M  _ N A M E >-------------------------------------



Assembles value and symbol according to the order specified in the properties table for this currency code



]]



local function make_short_form_name (amount, code, linked, passthrough)

	local symbol;

	local position = propertiescode].position;



	if linked then

		symbol = string.format ('[[%s|%s]]', propertiescode].page, propertiescode].symbol);	-- make wikilink of page and symbol

	else

		symbol = propertiescode].symbol;

	end



	if not passthrough then

		amount = lang:formatNum (tonumber(amount));								-- add appropriate comma separators

	end

	

	amount = amount:gsub ('^%-', '−');											-- replace the hyphen with unicode minus



	if 'b' == position then														-- choose appropriate format: unspaced before the amount

		return string.format ('%s%s', symbol, amount);

	elseif 'bs' == position then												-- spaced before the amount

		return string.format ('%s&nbsp;%s', symbol, amount);

	elseif 'a' == position then													-- unspaced after the amount

		return string.format ('%s%s', amount, symbol);

	elseif 'as' == position then												-- spaced after the amount

		return string.format ('%s&nbsp;%s', amount, symbol);

	elseif 'd' == position then													-- special case that replaces the decimal separator with symbol (Cifrão for CVE is the only extant case)

		if passthrough then

			return string.format('%s%s', symbol, amount)

		end

		

		local digits, decimals;													-- this code may not work for other currencies or on other language wikis

		if amount:match ('[%d,]+%.%d+') then									-- with decimal separator and decimals

			digits, decimals = amount:match ('([%d,]+)%.(%d+)')

			amount = string.format ('%s%s%s', digits, symbol, decimals);		-- insert symbol

		elseif amount:match ('[%d,]+%.?$') then									-- with or without decimal separator

			digits = amount:match ('([%d,]+)%.?$')

			amount = string.format ('%s%s00', digits, symbol);					-- add symbol and 00 ($00)

		end

		amount = amount:gsub (',', '%.');										-- replace grouping character with period

		return amount;

	end

	return amount .. ' <span style="font-size:inherit" class="error">{{currency}} – definition missing position ([[Template:Currency/doc#Error_messages|help]])</span>';	-- position not defined

end





--[[--------------------------< M A K E _ N A M E >----------------------------------------------------------



Make a wikilink from the currency's article title and its plural (if provided).  If linked is false, returns only

the article title (unlinked)



]]



local function make_name (linked, page, plural)

	if not linked then

		if not is_set (plural) then

			return page;														-- just the page

		elseif 's' == plural then												-- if the simple plural form

			return string.format ('%ss', page);									-- append an 's'

		else

			return plural;														-- must be the complex plural form (pounds sterling v. dollars)

		end

	else

		if not is_set (plural) then

			return string.format ('[[%s]]', page);

		elseif 's' == plural then												-- if the simple plural form

			return string.format ('[[%s]]s', page);

		else

			return string.format ('[[%s|%s]]', page, plural);					-- must be the complex plural form (pounds sterling v. dollars)

		end

	end

end





--[[--------------------------< M A K E _ L O N G _ F O R M  _ N A M E >---------------------------------------



assembles a long-form currency name from amount and name from the properties tables; plural for all values not equal to 1



]]



local function make_long_form_name (amount, code, linked, passthrough)

	local name, formatted;

	

	if not is_set (propertiescode].page) then

		return '<span style="font-size:inherit" class="error">{{currency}} – definition missing page ([[Template:Currency/doc#Error_messages|help]])</span>';

	end



	if not passthrough then

		amount = tonumber (amount);												-- make sure it's a number

	end



	if 1 == amount then

		name = make_name (linked, propertiescode].page);						-- the singular form

	elseif is_set (propertiescode].plural) then								-- plural and there is a plural form

		name = make_name (linked, propertiescode].page, propertiescode].plural);

	else

		name = make_name (linked, propertiescode].page);						-- plural but no separate plural form so use the singular form

	end

	

	if not passthrough then

		formatted = lang:formatNum (amount)

	else 

		formatted = amount

	end



	return string.format ('%s %s', formatted, name);							-- put it all together

end





--[[--------------------------< R E N D E R _ C U R R E N C Y >------------------------------------------------



Renders currency amount with symbol or long-form name.



Also, entry point for other modules.  Assumes that parameters have been vetted; amount is a number, code is upper

case string, long_form is boolean; all are required.



]]



local function render_currency (amount, code, long_form, linked, fmt, passthrough)

	local name;

	local result;



	presentation = mw.loadData ('Module:Currency/Presentation');				-- get presentation data



	if presentation.currency_propertiescode then								-- if code is an iso 4217 code

		properties = presentation.currency_properties;

	elseif presentation.code_translationcode then								-- not iso 4217 but can be translated

		code = presentation.code_translationcode];								-- then translate

		properties = presentation.currency_properties;

	elseif presentation.non_standard_propertiescode then						-- last chance, is it a non-standard code?

		properties = presentation.non_standard_properties;

	else

		return '<span style="font-size:inherit" class="error">{{currency}} – invalid code ([[Template:Currency/doc#Error_messages|help]])</span>';

	end





	if long_form then

		result = make_long_form_name (amount, code, linked, passthrough);								-- 

	else

		result = make_short_form_name (amount, code, linked, passthrough);

	end

	

	if 'none' == fmt then														-- no group separation

		result = result:gsub ('(%d%d?%d?),', '%1');								-- strip comma separators

	elseif 'gaps' == fmt then													-- use narrow gaps

		result = result:gsub ('(%d%d?%d?),', '<span style="margin-right:.25em;">%1</span>');	-- replace comma seperators

	elseif fmt and 'commas' ~= fmt then											-- if not commas (the default) then error message

		return '<span style="font-size:inherit" class="error">{{currency}} – invalid format ([[Template:Currency/doc#Error_messages|help]])</span>';

	end



	return result;																-- done

end



--[[--------------------------< P A R S E _ F O R M A T T E D _ N U M B E R >----------------------------------



replacement for lang:parseFormattedNumber() which doesn't work; all it does is strip commas.



This function returns a string where all comma separators have been removed from the source string.  If the source

is malformed: has characters other than digits, commas, and decimal points; has too many decimal points; has commas

in in appropriate locations; then the function returns nil.



]]



local function parse_formatted_number (amount)

	local count;

	local parts = {};

	local digits = {};

	local decimals;

	local sign = '';

	local _;

	

	if amount:find ('[^%-−%d%.,]')	then										-- anything but sign, digits, decimal points, or commas

		return nil;

	end

	

	amount = amount:gsub ('−', '-');											-- replace unicode minus with hyphen

	

	_, count = amount:gsub('%.', '')											-- count the number of decimal point characters 

	if 1 < count then

		return nil;																-- too many dots

	end



	_, count = amount:gsub(',', '')												-- count the number of grouping characters 

	if 0 == count then

		return amount;															-- no comma separators so we're done

	end;

	

	if amount:match ('[%-][%d%.,]+') then										-- if the amount is negative

		sign, amount = amount:match ('([%-])([%d%.,]+)');						-- strip off and save the sign

	end



	parts = mw.text.split (amount, '.', true);									-- split amount into digits and decimals

	decimals = table.remove (parts, 2) or '';									-- if there was a decimal portion, remove from the table and save it



	digits = mw.text.split (parts1], ',')										-- split amount into groups 

	for i, v in ipairs (digits) do												-- loop through the groups

		if 1 == i then															-- left-most digit group

			if (3 < v:len() or not is_set (v)) then								-- first digit group: 1, 2, 3 digits; can't be empty string (first char was a comma)

				return nil;

			end

		else

			if v and 3 ~= v:len() then											-- all other groups must be three digits long

				return nil;	

			end

		end

	end



	return sign .. table.concat (digits) .. '.' .. decimals;					-- reassemble without commas and return

end





--[[--------------------------< C O N V E R T _ S T R I N G _ T O _  N U M E R I C >------------------------------------------------



Converts quantified number/string combinations to a number e.g. 1 thousand to 1000.



]]



local function convert_string_to_numeric (amount)

	local quantifiers = {['thousand' = 1000, 'million' = 1000000, 'm' = 1000000, 'billion' = 1000000000, 'b' = 1000000000, 'trillion' = 1000000000000};



	

	local n, q = amount:match ('([%-−]?[%d%.,]+)%s*(%a+)$');					-- see if there is a quantifier following a number; zero or more space characters

	if nil == n then

		n, q = amount:match ('([%-−]?[%d%.,]+)&nbsp;(%a+)$')					-- see if there is a quantifier following a number; nbsp html entity ({{format price}} output

	end

	if nil == n then return amount end;											-- if not <number><space><quantifier> return amount unmolested

	

	n = n:gsub (',', '');														-- strip comma separators if present

	q = q:lower();																-- set the quantifier to lower case



	if nil == quantifiersq then return amount end;							-- if not a recognized quantifier

	

	return tostring (n * quantifiersq]);										-- return a string, not a number

end





--[[--------------------------< C U R R E N C Y >--------------------------------------------------------------



Template:Currency entry point.  The template takes three parameters:

	positional (1st), |amount=, |Amount=	: digits and decimal points only

	positional (2nd), |type=, |Type=		: code that identifies the currency

	|first=									: uses currency name instead of symbol



]]



local function currency (frame)

	local args = require('Module:Arguments').getArgs (frame);



	local amount, code;

	local long_form = false;

	local linked = true;

	local passthrough = false;



	if not is_set (args1]) then

		return '<span style="font-size:inherit" class="error">{{currency}} – invalid amount ([[Template:Currency/doc#Error_messages|help]])</span>';

	end

	

--	amount = lang:parseFormattedNumber(args[1]);								-- if args[1] can't be converted to a number then error (this just strips grouping characters)

--	if args[1]:find ('[^%d%.]') or not amount then								-- non-digit characters or more than one decimal point (because lag:parse... is broken)

--		return '<span style="font-size:inherit" class="error">{{currency}} – invalid amount ([[Template:Currency/doc#Error_messages|help]])</span>';

--	end



	-- This allows us to use {{currency}} while actually following [[MOS:CURRENCY]] as regards "billion", "million", "M", "bn", etc.

	if not (args'passthrough' == 'yes') then -- just pass whatever string is given through.

		amount = convert_string_to_numeric (args1]);

		amount = parse_formatted_number(amount);								-- if args[1] can't be converted to a number then error

		if not amount then

			return '<span style="font-size:inherit" class="error">{{currency}} – invalid amount ([[Template:Currency/doc#Error_messages|help]])</span>';

		end

	else

		amount = args1

	end

	

	if not is_set(args2]) then													-- if not provided

		code = 'USD';															-- default to USD

	else

		code = args2]:upper();													-- always upper case; used as index into data tables which all use upper case

	end

	

	if args3 then																-- this is the |first= parameter  TODO: make this value meaningful? y, yes, true?

		long_form = true;

	end

	

	if 'no' == args4 then														-- this is the |linked= parameter; defaults to 'yes'; any value but 'no' means yes

		linked = false;

	end



	return render_currency (amount, code, long_form, linked, args'fmt'], (args'passthrough' == 'yes'))

end



return {

	currency = currency,														-- template entry point

	_render_currency = render_currency,											-- other modules entry point

	}