Модуль:Валюта

Материал из свободной русской энциклопедии «Традиция»
Перейти к навигации Перейти к поиску

Служебный модуль для шаблонов {{Измеряется деньгами}}, {{Загрузить курсы валют}} и {{Количество людей}}.

Функции:

Тест

      • По одной:
        • 0,013495276653171 долларов, USD, доллар, доллара, ,
      • Префикс:
        • $ 0,013495276653171
      • Кратные 1000:
        • 1,3495276653171E-5 тыс долларов, тыс USD, тыс. долларов, тыс. USD, тысяч долларов, тысяч USD, тысячи долларов, тысячи USD, тысяча долларов, тысяча USD
      • Кратные 1000000:
        • 1,3495276653171E-8 млн долларов, млн USD, млн. долларов, млн. USD, миллионов долларов, миллионов USD, миллиона долларов, миллиона USD, миллион долларов, миллион USD
      • Кратные 1000000000:
        • 1,3495276653171E-11 млрд долларов, млрд USD, млрд. долларов, млрд. USD, миллиардов долларов, миллиардов USD, миллиарда долларов, миллиарда USD, миллиард долларов, миллиард USD
      • Кратные 1000000000000:
        • 1,3495276653171E-14 трлн долларов, трлн USD, трлн. долларов, трлн. USD, триллионов долларов, триллионов USD, триллиона долларов, триллиона USD, триллион долларов, триллион USD

--[[
	Пересчёт валют, задание единиц измерения для валют, загрузка курсов валют:
--]]

-- Dependencies:
local append = require 'Module:Array'.table_append
local ask = mw.smw.ask
local gsub, match, split = mw.ustring.gsub, mw.ustring.match, mw.text.split

-- Predeclaration:
local data

-- Utility functions:
local concat = table.concat
local function glue (...)
	local ret = {}
	for _, val in ipairs {...} do
		ret [#ret + 1] = type (val) == 'table' and concat (val, ', ') or val
	end
	return concat (ret, ', ')
end	-- local function glue (...)

local lc, sub = mw.ustring.lower, mw.ustring.sub
local function lcfirst (str)	
	return lc (sub (str, 1, 1)) .. sub (str, 2, -1)
end	-- local lcfirst (str)	

local function formatNumeralError (message, ...)
    if select('#', ... ) > 0 then
        message = string.format(message, ...)
    end
    return "<span class=\"error\">" .. message .. "</span>"
end

--[[
НАЗНАЧЕНИЕ:  Эта функция объявляет валюту.
 
ПАРАМЕТРЫ:	names	-- массив символов и названий валюты,
			rate	-- курс валюты к рублю.

ВЫВОД:		нет.
 
ВОЗВРАТ:	0, сообщение об ошибке - Ошибка из-за неверного аргумента
            строка                 - Объявление единиц измерения для валюты
 
ПРИМЕЧАНИЕ:  Все проверки аргумента уже проведены.
--]]
local function implementCurrency (rate, plurals, singulars, duals, prefixes, abbr, maximum)
	local lang = mw.getContentLanguage ()
	local declarations = {}
	if #singulars > 0 then
		declarations [#declarations + 1] = '*** По одной:'
		declarations [#declarations + 1] =  '**** [[' .. data.prop .. '::' .. lang:formatNum (1 / rate) .. ' ' .. glue (plurals, singulars, duals, abbr) .. ']]'
	end
	if #prefixes > 0 then
		declarations [#declarations + 1] = '*** Префикс:'		
		declarations [#declarations + 1] =  '**** [[' .. data.prop .. '::' .. glue (prefixes) .. ' ' .. lang:formatNum (1 / rate) .. ']]'
	end
	if #plurals > 0 then
		for _, settings in ipairs (data.mils) do
			if not maximum or settings.ratio <= maximum then
				local units = {}
				for __, numeral in ipairs (settings.numerals) do
					for ___, name in ipairs (append (plurals, abbr)) do
						units [#units + 1] = numeral .. ' ' .. name
					end
				end
				declarations [#declarations + 1] = '*** Кратные ' .. tostring (settings.ratio) .. ':'
				declarations [#declarations + 1] =  '**** [[' .. data.prop .. '::' .. lang:formatNum (1 / rate / settings.ratio) .. ' ' .. glue (units) .. ']]'
			end	-- if not max or settings.ratio <= maximum
		end	-- for _, settings in ipairs (data.mils)
	end	-- if #plurals > 0
	return declarations
end		-- local function implementCurrency (rate, plurals, singulars, duals, prefixes, abbr, max)
 
-- Загрузка аргументов и вызов реализации:
local function declareCurrency (args)
	local rate = 1
	local singulars, prefixes, plurals, duals, abbrs = {}, {}, {}, {}, {}
	local maximum
    
	-- Загрузка и проверка аргументов:
	local params = data.API.params
	local change = false
	-- Guarantee order:
	for arg, value in pairs (args) do
		local split = split (value, '%s,' )
		local param = params [arg]
		if param == 'prefix' then
			prefixes = append (prefixes, split)
		elseif param == 'singular' then
			singulars	= append (singulars, split)
		elseif param == 'dual' then
			duals	= append (duals, split)
		elseif param == 'abbr' then
			abbrs	= append (abbrs, split)
		elseif param == 'max' then
			maximum	= tonumber (value)
		elseif tonumber (value) ~= nil then
			-- Exchange rate:
			rate = tonumber (value)
		else
			-- Currency symbol or name:
			plurals = append (plurals, split)
		end	-- 	if param == 'singular' or param == 'prefix'
	end
	return table.concat (implementCurrency (rate, plurals, singulars, duals, prefixes, abbrs, maximum), '\n')
	
end		-- local function declareCurrency (args)

local function getRates (basic)
	return append (ask {
		'[[' .. basic .. ']]'
	  , '?Название=full'
	  , '?Код ISO 4217=iso'	  
	  , '?Символ валюты=symbol'
	  , '?Сокращается как=abbr'
	  , '?Валюта в РП ДЧ=dual'
	  , '?Валюта в РП МЧ=plural'
	  , '?'
	  , link	= 'none'
	}, ask {
		'[[Целевая валюта::' .. basic .. ']]'
	 .. '[[Исходная валюта.Важность::>>0]]'
	  , '?Коэффициент исходной валюты=quantity'
	  , '?Курс=rate'
	  , '?Дата курса=date'
	  , '?Исходная валюта.Название=full'
	  , '?Исходная валюта.Код ISO 4217=iso'	  
	  , '?Исходная валюта.Символ валюты=symbol'
	  , '?Исходная валюта.Сокращается как=abbr'
	  , '?Исходная валюта.Валюта в РП ДЧ=dual'
	  , '?Исходная валюта.Валюта в РП МЧ=plural'
	  , '?'
	  , limit	= 10
	  , link	= 'none'
	  , sort	= 'Дата курса,Исходная валюта.Важность'
	  , order	= 'desc,desc'
	})	-- return append ...
end	-- local function getRates (basic)

local function declareAll (args)
	local basic = args [1]
	local annotations = {}
	for _, record in ipairs (getRates (basic)) do
		local full = {}
		for i, str in ipairs (type (record.full) == 'table' and record.full or {record.full}) do
			full [i] = lcfirst (str)
		end
		if record.full then
			annotations [#annotations + 1] = '**' .. glue (record.full) .. '\n'
										  .. table.concat (implementCurrency (
				(record.rate or 1) / (record.quantity or 1)
			  , append (record.plural)
			  , append (full, record.iso, record.symbol)
			  , append (record.dual)			  
			  , append (record.symbol)
			  , append (record.abbr)
			), '\n')
		end
	end	-- for _, record in ipairs (getRates ())
	return table.concat (annotations, '\n')
end	-- local function declareAll (args)

-- Пересчёт валют, задание единиц измерения для валют:
data = {

	API = {
		-- Экспортируемые имена функций:
		exports = {
			[{'объявить', 'Объявить', 'declare', 'Declare'}] = declareCurrency,
			[{'declareAll', 'auto', 'авто'}] = declareAll,
		},	
		params = {
			['префикс'] = 'prefix', prefix = 'prefix',
			['единственное'] = 'singular', singular = 'singular',
			['двойственное'] = 'dual', dual = 'dual',
			['сокр'] = 'abbr', abbr = 'abbr',
			['макс'] = 'max', max = 'max'
		}
	},	-- API
	
	-- Ошибки:
    noNumber    = "Передайте аргумент",

    -- Аргументы:
    argNumber = 1,           -- число
    
    -- Кратные:
	mils = {
		--[1]				= {''},
		{ratio = 1000,			numerals = {'тыс', 'тыс.', 'тысяч', 'тысячи', 'тысяча'}},
		{ratio = 1000000,		numerals = {'млн', 'млн.', 'миллионов', 'миллиона', 'миллион'}},
		{ratio = 1000000000,	numerals = {'млрд', 'млрд.', 'миллиардов', 'миллиарда', 'миллиард'}},
		{ratio = 1000000000000,	numerals = {'трлн', 'трлн.', 'триллионов', 'триллиона', 'триллион'}}		
	},	-- 	mils = {...}

	prop = 'Относится к'
}	-- data = {...}

--------------------------------------------------------------------------------

-- Загрузка курсов валют:

local services = {}

services ['currency'] = {
	template	= 'Записать курс валюты'
  , subobject	= 'Содержит курс валюты'
  , cache		= 10 * 365.2475 * 24 * 60 * 60	-- ten years.
}	-- services ['currency']

services ['RUB.current'] = mw.clone (services ['currency'])
services ['RUB.current'].URL = 'http://www.cbr.ru/scripts/XML_daily.asp' --'https://www.cbr-xml-daily.ru/daily_utf8.xml'
services ['RUB.current'].xpaths = {
	['Коэффициент исходной валюты']	= '/ValCurs/Valute/Nominal'
  , ['Код исходной валюты']			= '/ValCurs/Valute/CharCode'
  , ['Курс']						= '/ValCurs/Valute/Value'
  , ['Целевая валюта']				= 'Рубль'
  , ['Код целевой валюты']			= 'RUB'
  , ['Дата курса']					= '/ValCurs/@Date'    
  , ['Источник курса']				= '$url$'
}	-- services ['RUB.current'].xpaths
services ['RUB.current'].postprocess = {
	['Курс'] = function (str)
		return gsub (str, '%s+', '')
	end
}

services ['RUB.historical'] = mw.clone (services ['RUB.current'])
services ['RUB.historical'].URL		= 'http://www.cbr.ru/scripts/XML_daily.asp?date_req=$date#d.m.Y$' -- 'https://www.cbr-xml-daily.ru/archive/$date#Y/m/d$/daily_utf8.xml'
services ['RUB.historical'].cache	= 365.25 * 24 * 60 * 60	-- one year.

services ['FOR.current'] = mw.clone (services ['currency'])
services ['FOR.current'].URL	= 'http://www.floatrates.com/daily/$iso$.xml'
services ['FOR.current'].xpaths = {
  	['Дата курса']					= '/channel/pubDate'
  , ['Коэффициент исходной валюты']	= '1'
  , ['Курс']						= '/channel/item/inverseRate'
  , ['Код исходной валюты']			= '/channel/item/targetCurrency'
  , ['Код целевой валюты']			= '/channel/baseCurrency'
  , ['Источник курса']				= '/channel/link'
}	-- services ['FOR.daily'].xpaths
services ['FOR.current'].postprocess = {
	['Курс'] = function (str)
		return gsub (gsub (str, '^%.', '0,'), '%.', ',')
	end
}

services ['FOR.historical'] = mw.clone (services ['FOR.current'])
services ['FOR.historical'].URL 						= 'https://www.floatrates.com/historical-exchange-rates.html?currency_date=$date#Y-m-d$&base_currency_code=$iso$&format_type=xml'
services ['FOR.historical'].cache						= 365.25 * 24 * 60 * 60	-- one year.
services ['FOR.historical'].xpaths ['Курс']				= '/channel/item/exchangeRate'
services ['FOR.historical'].xpaths ['Источник курса']	= '$url$'
services ['FOR.historical'].xpaths ['Обратный']			= 'yes'
services ['FOR.historical'].postprocess = {
	['Курс'] = function (str)
		return gsub (gsub (tostring (1 / tonumber (str)), '^%.', '0,'), '%.', ',')
	end
}

--------------------------------------------------------------------------------
local function substitute (str, symbols)
	local ret = str
	local lang = mw.getContentLanguage ()
	for symbol, value in pairs (symbols) do
		-- Formatted metasymbols:
		ret = gsub (ret, '%$' .. symbol .. '#(.-)%$', function (format)
			return lang:formatDate (format, value)
		end)
		-- Plain metasymbols:
		ret = gsub (ret, '%$' .. symbol .. '%$', value)
	end
	return ret
end	-- local function substitute (str, symbols)

local function pickService (iso, date)
	local historical = date and 'historical' or 'current'
	local service = services [(iso or 'FOR') .. '.' .. historical]
				 or services ['FOR.'		 .. historical]
	-- Substitute metasymbols:
	local symbols = {iso = iso, date = date}
	service.URL = substitute (service.URL, symbols)
	symbols.url = service.URL
	for key, param in pairs (service.xpaths) do
		service.xpaths [key] = substitute (param, symbols)
	end
	return service
end	-- local function pickService (iso, date)

--------------------------------------------------------------------------------

local function named_join (tbl, separator)
	local ret = {}
	for key, val in pairs (tbl) do
		ret [#ret + 1] = tostring (key) .. '=' .. tostring (val)
	end
	return table.concat (ret, separator or ',')
end	-- local function named_join (tbl, separator)

local function key2key_join (tbl, separator)
	local ret = {}
	for key, _ in pairs (tbl) do
		ret [#ret + 1] = tostring (key) .. '=' .. tostring (key)
	end
	return table.concat (ret, separator or ',')
end	-- local function key2key_join (tbl, separator)

local function downloadRates (frame, url, xpaths, cache)
	return frame:callParserFunction ('#get_web_data', {
		'use xpath'
	  , url					= url
	  , format				= 'xml'
	  , data				= xpaths
	  , ['cache seconds']	= cache
	})
end	-- local function downloadRates (frame, url, xpaths, cache)

local function split_xpaths (params)
	local depths, max_depth = {}, 0
	-- Find the deepest xpath (it will fetch the most matches):
	for name, param in pairs (params) do
		local depth = 0
		if sub (param, 1, 4) ~= 'http' then
			depth = #gsub (param, '[^/]', '')
		end
		depths [name] = depth
		if depth > max_depth then max_depth = depth end
	end
	local xpaths, constants, variables = {}, {}, {}
	-- in {{#display_external_table}}, xpaths will either go to template head or to params:
	for name, param in pairs (params) do
		if depths [name] == max_depth then
			variables [name] = name
			xpaths [name] = param
		elseif depths [name] == 0 then
			constants [name] = param
		else
			constants [name] = '{{#external_value:' .. name .. '}}'
			xpaths [name] = param
		end
	end	-- for name, xpath in pairs (xpaths)
	return named_join (xpaths, ',')
		 , constants ~= {} and '|' .. named_join (constants, '|') or ''
		 , key2key_join (variables, ',')
end	-- local function split_xpaths (params)

local function saveRates (frame, template, constants, variables)
	return '\n*' ..
		   frame:callParserFunction ('#display_external_table', {''
			 , template	= template .. constants
			 , data		= variables
			 , delimiter= '*'
		   })
end	-- local function saveRates (frame, template, constants, variables)

--------------------------------------------------------------------------------

local function download (frame)
	local page	= mw.title.getCurrentTitle ().fullText
	local iso	= match (page, '%f[^/\0][A-Z][A-Z][A-Z]%f[/\0]')
	local date	= match (page, '[0-3]%d%.[01]%d%.%d%d%d%d$')
	local service	= pickService (iso, date)
	local xpaths, constants, variables = split_xpaths (service.xpaths)	
	local header = '\n== Курсы валют к валюте «' .. (iso or '') .. '» '
				.. (date and ': ' .. mw.getContentLanguage ():formatDate ('l, d xg Y', date) or '')
				.. '==\n'	
	return header
		.. downloadRates	(frame, service.URL, xpaths, service.cache)
		.. saveRates		(frame, service.template, constants, variables)

end	-- local function download (frame)

local function test (frame)
	local page = mw.title.getCurrentTitle ().fullText
	local iso	= mw.ustring.match (page, '%f[^/\0][A-Z][A-Z][A-Z]%f[/\0]') or 'USD'
	local date	= mw.ustring.match (page, '[0-3]%d%.[01]%d%.%d%d%d%d$')
			   or tostring (os.date ())
	local service	= pickService (iso, date)
	local xpaths, constants, variables = split_xpaths (service.xpaths)
	local dump = mw.dumpObject
	return 'ISO: ' .. dump (iso) .. '\n\n'
		.. 'date: ' .. dump (date) .. '\n\n'
		.. 'service:\n' .. dump (service) .. '\n\n'
		.. 'data param for {{#get_web_data}}: ' .. dump (xpaths) .. '\n\n'
		.. 'template:' .. dump (service.template .. '|' .. constants) .. '\n\n'
		.. 'data param for {{#display_external_table}}: ' .. dump (variables) .. '\n\n'
end	-- local function test (frame)

--------------------------------------------------------------------------------
-- Export function:
local m = {}

for aliases, func in pairs (data.API.exports) do
	for _, alias in ipairs (aliases) do
		m [alias] = function (frame)
			return func ((frame or {}).args)
		end
    end
end	-- for aliases, func in pairs (data.API.exports)

m ['курсы']	= download
m ['test']	= test

return m