Jump to content

Module:SimpleArgs

Ón Vicipéid, an chiclipéid shaor.

Documentation for this module may be created at Module:SimpleArgs/doc

local p = {}

--[[
Version 2020-08-28
1. Get parameters (whether the parameters are called with #invoke or from a template)
2. Get parameters (of several types with their verification) from frame parameters.
	ParId parameter is identified by a position (1, 2, 3 ... ), single name (|name= |other name=) or table of two names for each parameter.
	This last options is used in translations that retain the original language (or main), usually English, i.e.:
	local keywords = { -- in this case with Spanish
		name =		 {'name',		'nombre'},
		other name = {'other name',	'otro nombre'},
	}
	Then the parameter can be obtained with p.GetStr_Par (Args, keywords[name])
--]]

local RS = {
	NotFoundArgName = 'NotFoundArgName',
	SIsNotNumber = 'SIsNotNumber',
	NIsNotPosNumber = 'NIsNotPosNumber',
	NIsNotZeroOrPosNumber = 'NIsNotZeroOrPosNumber',
	NIsNotInt = 'NIsNotInt',
	NIsNotInRange = 'NIsNotInRange',
	NIsLessThan = 'NIsLessThan',
	NIsGreaterThan = 'NIsGreaterThan',
	InvalColorLength = 'InvalColorLength',
	InvalColorChar = 'InvalColorChar',
	InvalColorName = 'InvalColorName',
	STabIsNotInRange = 'STabIsNotInRange',
	STabFewItems = 'STabFewItems',
	SIsNotAssigned = 'SIsNotAssigned',
	CharNotFoundInPattern = 'CharNotFoundInPattern',
	SNotFoundInTab = 'SNotFoundInTab',
	EmptyValue = 'EmptyValue',
	SizeUnitRequired = 'SizeUnitRequired',
	InvalSizeUnit = 'InvalSizeUnit',
	SizeWithoutNumber = 'SizeWithoutNumber',
	InvalAlign = 'InvalAlign',
	InvalBool = 'InvalBool',
	left = 'left',
	right = 'right',
	top = 'top',
	bottom = 'bottom',
	center = 'center',
	Yes = 'Yes',
	No = 'No',
	PossibleValues = 'PossibleValues',
}

local SD = require('Module:SimpleDebug')
local I18n = 'SimpleArgs'

local i18n = {
	[RS.NotFoundArgName]		= "The parameter name \"$1\" is invalid",
	[RS.SIsNotNumber]			= "\"$1\" is not a number",
	[RS.NIsNotPosNumber] 		= "$1 is not a positive number",
	[RS.NIsNotZeroOrPosNumber]	= "$1 is not zero or a positive number",
	[RS.NIsNotInt]				= "$1 is not an integer.",
	[RS.NIsNotInRange]			= "$1 is not between $2 and $3",
	[RS.NIsLessThan] 			= "$1 is less than $2",
	[RS.NIsGreaterThan]			= "$1 is greater than $2",
	[RS.InvalColorLength]		= 'Invalid color code length, after "#", 6 valid characters (0-F) are required (found "$1")',
	[RS.InvalColorChar]			= 'Invalid character in the code color (from "$1")',
	[RS.InvalColorName]			= 'Color name ($1) is not a valid HTML color name',
	[RS.STabIsNotInRange]		= "The item number ($1) is not between $2 and $3",
	[RS.STabFewItems]			= "There are too few elements ($1), at least $2 are required",
	[RS.SIsNotAssigned]			= "Required parameter is missing",
	[RS.CharNotFoundInPattern]	= "\"$1\" is not a character of \"$2\"",
	[RS.SNotFoundInTab]			= "\"$1\" is not found in \"$2\"",
	[RS.EmptyValue]				= "\"$1\" has not a value",
	[RS.SizeUnitRequired]		= "Size unit required (em, px or %)",
	[RS.InvalSizeUnit]			= "Invalid unit ($1)",
	[RS.SizeWithoutNumber]		= "Without number",
	[RS.InvalAlign]				= "Invalid alignment ($1)",
	[RS.InvalBool]				= "Invalid boolean ($1)",
	[RS.left]					= "left",
	[RS.right]					= "right",
	[RS.top] 					= "top",
	[RS.bottom]					= "bottom",
	[RS.center]					= "center",
	[RS.Yes] 					= "yes|y|true|t|1",
	[RS.No]						= "no|n|false|f|0",
	[RS.PossibleValues]			= "Possible values: $1",
}

function p.HasValue (v)
	return (v ~= nil) and (v ~= '')
end

--http://lua-users.org/wiki/CopyTable:
function p.deep_copy_table (orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[p.deep_copy_table(orig_key)] = p.deep_copy_table(orig_value)
        end
        setmetatable(copy, p.deep_copy_table(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end --deep_copy_table

-- Credit to http://stackoverflow.com/a/1283608/2644759
-- cc-by-sa 3.0
function p.tableMerge (t1, t2)
	for k,v in pairs(t2) do
		if type(v) == "table" then
			if type(t1[k] or false) == "table" then
				p.tableMerge(t1[k] or {}, t2[k] or {})
			elseif ((v ~= nil) and (t1[k] == nil)) or p.HasValue(v) then --old version ...else t1[k] = v end
				t1[k] = v
			end
		elseif ((v ~= nil) and (t1[k] == nil)) or p.HasValue(v) then --old version ...else t1[k] = v end
			t1[k] = v
		end
	end
	return t1
end --tableMerge

local wiki_langcode = mw.language.getContentLanguage().code	
function p.get_lang (langcode)
	--form Module:Wikidades
	if mw.language.isKnownLanguageTag(langcode or '') == false then
		if not mw.title.getCurrentTitle().isContentPage then
			local cframe = mw.getCurrentFrame()
			langcode = cframe:preprocess('{{int:lang}}')
		end
		if mw.language.isKnownLanguageTag(langcode or '') == false then
			langcode = wiki_langcode
		end
	end	
	return langcode
end --get_lang
p.lang_to_use = p.get_lang ()

function p.I18nName (ModName)
	local i18n_suffix0 = "/i18n"
	local i18n_suffix = ''
	if p.lang_to_use ~= wiki_langcode then 
		i18n_suffix = i18n_suffix0..'/'..p.lang_to_use 
	else
		i18n_suffix = i18n_suffix0
	end
	local FN = "Module:"..ModName
	local title = mw.title.new (FN..i18n_suffix)
	if title.exists then
		return FN..i18n_suffix
	else
		FN = FN..i18n_suffix0
		title = mw.title.new (FN)
		if title.exists then
			return FN
		else	
			error (string.format('Not found "%s"',FN)) -- Translation not required
		end	
	end
end --I18nName

function p.loadI18n (ModName, DefTable)
	local FN = p.I18nName (ModName)
	return p.tableMerge(DefTable, require(FN).i18n)
end --loadI18n

---

function p.I18nStrParams (S, ...)
	local result = mw.message.newRawMessage (S, ...)
	return result:plain()
end

function p.TrimWdTab (tab)
	for k, i in ipairs(tab) do
		tab[k] = mw.text.trim (i)
	end
	return tab
end	--TrimWdTab

function p.I18nParamsTab (Wds, MaxTrans)
	local tab = mw.text.split (Wds, '|')
	if (MaxTrans ~= nil) and (#tab > MaxTrans) then
		error (string.format('Found %s translations for "%s"',#tab,S)) -- Translation not required
	end
	return p.TrimWdTab (tab)
end --I18nParamsTab

function p.I18nParamsTabCS (Wds, CaseSensitive)
	CaseSensitive = ((CaseSensitive ~= nil) and (CaseSensitive == true)) or true
	if not CaseSensitive then
		Wds = string.lower (Wds)
	end	
    tab = mw.text.split (Wds, '|')
	return p.TrimWdTab (tab)
end --I18nParamsTabCS

function p.SFoundInWdTab (WdTable, val, CaseSensitive, S)
	if (S == nil) or (S == '') then
		error('Not parameters trying to find "'..val..'"') --It doesn't require translation, only for degug
	end	
	local Arr = p.I18nParamsTabCS (WdTable[S], CaseSensitive)
	if not CaseSensitive then
		val = string.lower (val)
	end	
	for _, W in ipairs(Arr) do
		if W == val then
			return true
		end	
	end	
	return false
end --SFoundInWdTab

function p.IdxFromWdTab (WdTable, val, CaseSensitive, ...)
	local Arr = unpack(arg)
	if Arr == nil then
		error('Not parameters trying to find "'..val..'"') --It doesn't require translation, only for degug
	end	
	local Idx = 0
	for I, W in ipairs(Arr) do
		if p.SFoundInWdTab (WdTable, val, CaseSensitive, W) then
			Idx = I
			break
		end	
	end	
	return Idx
end	--IdxFromWdTab

function p.JoinI18nTables (tabs)
	local res = {}
	for _, v in ipairs(tabs) do
		for _, vv in pairs(v) do
			table.insert (res, vv)
		end
	end	
	return res
end	--JoinTables

----------

i18n = p.loadI18n (I18n, i18n)

local function I18nStr (S, ...)
	return p.I18nStrParams (i18n[S], ...)
end

function p.i18n_vals (frame)
    local args = p.GetArgs (frame)
    local res = i18n[p.RStr_Par(args,1)]
    if res == nil then
    	error ('Invalid word')
    else
    	return res
    end	
end --i18n_vals	

----------

p.Error = {
	handle = false,
	yes = false,
	msg = '',
	samesize = false
	}
function p.MsgError (S)
	if S == nil then
		if p.Error.samesize then
			return '<span style="color:red;font-weight:bold">'..p.Error.msg..'</span>'
		else	
			return '<strong class="error">'..p.Error.msg..'</strong>'
		end
	else
		if p.Error.handle then
			p.Error.msg = S
			p.Error.yes = true
		else
			error (S, 0)
		end
	end
end--MsgError

function p.GetAndClearError ()
	local E = p.Error.msg
	p.Error.msg = ''
	p.Error.yes = false
	return E
end	--GetAndClearError

function p.TableSize (tab)
	Count = 0
	for _, v in pairs(tab) do
		Count = Count + 1
	end
	return Count
end --TableSize

function p.TableWithItems (tab)
	if #tab > 0 then
		return true
	else
		for _, v in pairs(tab) do
			return true
		end
		return false
	end	
end	--TableWithItems

function p.GetArgs (frame, HandleError, SameSize)
	local origArgs
	if frame == mw.getCurrentFrame() then
		-- We're being called via #invoke. If the invoking template passed any parameters,
		-- use them. Otherwise, use the parameters that were passed into the template.
		origArgs = frame:getParent().args
		for _, v in pairs( frame.args ) do
			origArgs = frame.args
			break
		end
	else		
		-- We're being called from another module or from the debug console, so assume
		-- the parameters are passed in directly.
		origArgs = frame
	end
	NArgs = p.TableSize (origArgs)
	if (HandleError ~= nil) and (HandleError == true) then
		p.Error.handle = true
		if (SameSize ~= nil) and (SameSize == true) then
			p.Error.samesize = true
		end
	end
	if p.Error.handle and (NArgs == 0) then 
		p.MsgError ('')
	end
	return origArgs, NArgs
end --GetArgs

function p.GetArgsT (frame, HandleError, SameSize)
	--Get paramenters,num from template that call to module (returned 1st,2nd) and from module (returned 3rd,4th) 
	local origArgs, NArgs = p.GetArgs (frame, HandleError, SameSize) 
	pargs = frame:getParent().args
	NPArgs = p.TableSize(pargs)
	if  NPArgs == 0 then
		return pargs, NPArgs, origArgs, NArgs
	else
		return origArgs, NArgs, pargs, NPArgs
	end	
end --GetArgsT

function p.GetArgsTMerge (frame, HandleError, SameSize)
	local arg1, NArg1, arg2, NArg2 = p.GetArgsT (frame, HandleError, SameSize)
	local args = {}
	for k, val in pairs(arg1) do
		args[k] = val
	end	
	for k, val in pairs(arg2) do
		args[k] = val
	end	
	return args
end --GetArgsTMerge	

p.ParaSep = '/'
function p.ConcatParams (tab)
	return '"'..table.concat(tab,p.ParaSep)..'"'
end

local function ParamErrorS (ParId, S)
	if ParId == nil then
		error ('Not assigned ParId') --Not require translation, only debug
	elseif ParId ~= '' then
		local SParaId = ''
		if type(ParId)=='table' then
			SParaId = p.ConcatParams (ParId)
		else
			if type(ParId)=='number' then
				ParId = '#'..ParId
			end
			SParaId = ParId 
		end
		S = SParaId..' = '..S
	end
	p.MsgError (S)
end--ParamErrorS

local function ParamError (ParId, RS, Value)
	if Value == '' then
		Value = '""'
	end
	local S = I18nStr (RS, tostring(Value))
	ParamErrorS (ParId, S)
end--ParamError

----------

local function GetArgsPass (args)
	local ArgsPass = {}
	for k, v in pairs(args) do
		if type(k) == 'string' then
			table.insert (ArgsPass, k)
		end
	end	
	return ArgsPass
end --GetArgsPass

function p.ErrNotFoundArgName (v)
	p.MsgError (I18nStr (RS.NotFoundArgName, v))
end

function p.CheckParams (args, UsualArgs)
	if p.Error.yes then return end
	local ArgsPass = GetArgsPass (args)
	local UsualArgsC = p.deep_copy_table (UsualArgs)
	for _, v in ipairs(ArgsPass) do
		local found = false
		for kk, vv in pairs(UsualArgsC) do
			if type(vv) == 'table' then
				for j, w in ipairs(vv) do
					if v == w then
						found = true
						UsualArgsC[kk][j] = nil
						break
					end	
				end
			else
				if v == vv then
					found = true
					UsualArgsC[kk] = nil
				end	
			end	
			if found then
				break
			end	
		end	
		if not found then
			p.ErrNotFoundArgName (v)
			break
		end	
	end
end --CheckParams

p.HasChild = 'HasChild'
function p.CheckParamsM (args, UsualArgs, OtherArgs, ArgNamesIn1)
--[[ Warning!: UsualArgs is a table of table or parameters, 
	e.g.{args_main_module, args_shared_module1, args_shared_module2} or
		{args_main_module}
	In ArgNamesIn1 mode, each parameter has the name/s and type: e.g.
	["key"] = {"Arg_name", "i+"}   or
	["key"] = {{"Arg_name","arg_name"} "i+"}
	in this case "i+" indicates a positive integer.
--]]
	if p.Error.yes then return end
	if p.Error.handle then
		unknown_param = {}
	end	
	local ArgsPass = GetArgsPass (args)
	local flags_dupli_args = {}
	local flags_dupli_args_s = {}
	local dupli_args = {}
	local key = ''
	local UsualArgsC = p.deep_copy_table (UsualArgs)
	for _, v in ipairs(ArgsPass) do
		local found = false
		for kk, vv in ipairs(UsualArgsC) do
			if vv[p.HasChild] == true then
				for j, w in pairs(vv) do
					if j ~= p.HasChild then
						for jj, ww in pairs(w) do 
							if ArgNamesIn1 then
								ww = ww[1] --skip type
							end	
							if type(ww) == 'table' then
								for jjj, www in ipairs(ww) do
									if v == www then
										found = true
										key = jj
										break
									end	
								end	
							else
								if v == ww then
									key = jj
									found = true
								end	
							end	
							if found then
								break
							end	
						end	
					end
					if found then
						break
					end	
				end	
			else
				for j, w in pairs(vv) do
					if ArgNamesIn1 then
						w = w[1] --skip type
					end	
					if type(w) == 'table' then
						for jj, ww in ipairs(w) do 
							if v == ww then
								key = j
								found = true
								break
							end	
						end	
					else
						if v == w then
							key = j
							found = true
						end	
					end	
					if found then
						break
					end	
				end
			end	
			if found then
				break
			end	
		end
		if found then
			if flags_dupli_args[key] then
				table.insert (dupli_args, key..' ('..flags_dupli_args_s[key]..'-'..v..')')
			else   
		    	flags_dupli_args[key] = true
		    	flags_dupli_args_s[key] = v
	    	end
		end	
		if (not found) and (OtherArgs ~= nil) then
			if type(OtherArgs) == 'table' then
				for _, vv in ipairs(OtherArgs) do
					if v == vv then
						found = true
						break
					end	
				end	
			else
				found = v == vv
			end	
		end	
		if not found then 	
			p.ErrNotFoundArgName (v)
			if p.Error.handle then
				table.insert (unknown_param, p.GetAndClearError())
			else	
				break
			end	
		end	
	end
	if p.Error.handle and ((#unknown_param > 0) or (#dupli_args > 0)) then
		return unknown_param, dupli_args
	end	
end --CheckParamsM

----------

local function prepNum (N)
	return mw.getContentLanguage():formatNum (N)
end

function p.CheckNum (N, ParId, LimInf, LimSup)
	if (p.Error.yes) or (not N) then return end
	if ((LimInf ~= nil) and (N < LimInf)) and ((LimSup ~= nil) and (N > LimSup)) then
		ParamErrorS (ParId, I18nStr (RS.NIsNotInRange, prepNum(N), prepNum(LimInf), prepNum(LimSup)))
	elseif (LimInf ~= nil) and (N < LimInf) then
		ParamErrorS (ParId, I18nStr (RS.NIsLessThan, prepNum(N), prepNum(LimInf)))
	elseif (LimSup ~= nil) and (N > LimSup) then
		ParamErrorS (ParId, I18nStr (RS.NIsGreaterThan, prepNum(N), prepNum(LimSup)))
	end
end --p.CheckNum

function p.CheckNumIsInt (N, ParId, LimInf, LimSup)
	if p.Error.yes then return end
	if N == math.floor(N) then
		p.CheckNum (N, ParId, LimInf, LimSup)
	else
		ParamError (ParId, RS.NIsNotInt, prepNum(N))
	end
end --CheckNumIsInt

function p.CheckNumIsPos (N, ParId, LimInf, LimSup)
	if p.Error.yes then return end
	if N > 0 then
		p.CheckNum (N, ParId, LimInf, LimSup)
	else	
		ParamError (ParId, RS.NIsNotPosNumber, N)
	end
end --CheckNumIsPos

function p.CheckNumIsZeroOrPos (N, ParId, LimSup)
	if p.Error.yes then return end
	if N >= 0 then
		p.CheckNum (N, ParId, 0, LimSup)
	else	
		ParamError (ParId, RS.NIsNotZeroOrPosNumber, N)
	end
end --CheckNumIsZeroOrPos

-----

function p.CheckSIsNum (S, ParId, LimInf, LimSup)
	if p.Error.yes then return end
	local N = tonumber(S)
	if not N then
		ParamError (ParId, RS.SIsNotNumber, S)
	else
		p.CheckNum (N, ParId, LimInf, LimSup)
	end
end --CheckSIsNum

function p.CheckSIsInt (S, Where, LimInf, LimSup)
	if p.Error.yes then return end
   	p.CheckSIsNum (S, Where, LimInf, LimSup) 
	if p.Error.yes then return end
   	p.CheckNumIsInt (tonumber(S), Where)
end

function p.CheckSIsPosInt (S, ParId, LimInf, LimSup)
	if p.Error.yes then return end
   	p.CheckSIsInt (S, ParId, LimInf, LimSup) 
	if p.Error.yes then return end
   	p.CheckNumIsPos (tonumber(S), ParId)
end

function p.CheckSIsZeroOrPosInt (S, ParId, LimSup)
	if p.Error.yes then return end
   	p.CheckSIsInt (S, ParId, 0, LimSup) 
	if p.Error.yes then return end
   	p.CheckNumIsZeroOrPos (tonumber(S), ParId)
end

function p.NotAssignedValue (ParId)
	ParamErrorS (ParId, I18nStr (RS.SIsNotAssigned))
end

----------

p.OKEmptyVal = true
function p.Str_Par (Args, ParId, Default)
	if p.Error.yes then return end
	local S = nil
	if type(ParId) == 'table' then
		for _, W in ipairs(ParId) do
			S = Args[W]
			if S ~= nil then
				break
			end
		end
	else	
		S = Args[ParId]
	end
	if S == nil then
		if Default ~= nil then
			S = Default
		end
	else
		S = mw.text.trim (S)
		if S == '' then
			if Default ~= nil then
				S = Default
			elseif not p.OKEmptyVal then
				S = nil
			end	
		end
	end
	return S
end --Str_Par

function p.RStr_Par (Args, ParId, OKEmpty)
	if p.Error.yes then return end
	local S = p.Str_Par (Args, ParId)
	if (S == nil) or ((S == '') and ((OKEmpty == nil) or (not OKEmpty))) then
		p.NotAssignedValue (ParId)
	else
		return S
	end
end --RStr_Par

function p.Char_Par (Args, ParId, Pattern, Default)
	if p.Error.yes then return end
	local Char = p.Str_Par (Args, ParId, Default)
	if p.Error.yes then return end
	if Char ~= nil then
		local Valid = ((Default ~= nil) and (Char == Default)) or (string.len(Char) == 1) and (string.find(Pattern,Char) ~= nil)
		if not Valid then
			ParamErrorS (ParId, I18nStr (RS.CharNotFoundInPattern, Char, Pattern))
		end
	end
	return Char
end --Char_Par

function p.RChar_Par (Args, ParId, Pattern)
	if p.Error.yes then return end
	local Char = p.Char_Par (Args, ParId, Pattern)
	if p.Error.yes then return end
	if Char == nil then
		p.NotAssignedValue (ParId)
	else
		return Char
	end
end --RChar_Par

function p.NulOrWhitespace_Par (Args, ParId)
	if p.Error.yes then return end
	local S = p.Str_Par (Args, ParId)
	if p.Error.yes then return end
	return (S == nil) or (S == '')
end

local function PerhapsLow (CaseSens, Wd)
	if CaseSens then
		return Wd
	else
		return string.lower(Wd)
	end
end --PerhapsLow

function p.StrChkTab_Par0 (ParId, S, Tab, CaseSens, Default) 
	if (S ~= nil) and (S ~= Default) then
		local SIni = S
		S = PerhapsLow(CaseSens,S)
		local found = false
		for _, W in ipairs(Tab) do
			if S == PerhapsLow(CaseSens,W) then
				S = W
				found = true
				break
			end
		end
		if not found then
			ParamErrorS (ParId, I18nStr (RS.SNotFoundInTab, SIni, table.concat(Tab,', ')))
			S = nil
		end
	end
	return S
end --StrChkTab_Par0

function p.StrChkTab_Par (Args, ParId, Tab, CaseSens, Default)
	if p.Error.yes then return end
	local S = p.Str_Par (Args, ParId, Default)
	if p.Error.yes then return end
	return p.StrChkTab_Par0 (ParId, S, Tab, CaseSens, Default)
end --StrChkTab_Par

function p.RStrChkTab_Par (Args, ParId, Tab, CaseSens)
	if p.Error.yes then return end
	local S = p.StrChkTab_Par (Args, ParId, Tab)
	if p.Error.yes then return end
	if S == nil then
		p.NotAssignedValue (ParId)
	else
		return S
	end
end --RStrChkTab_Par

function p.StrIdxChkTab_Par (Args, ParId, Tab, CaseSens, Default)
	if p.Error.yes then return end
	local S = p.Str_Par (Args, ParId, Default)
	if p.Error.yes then return end
	if (S ~= nil) and (S ~= Default) then
		local SIni = S
		S = PerhapsLow(CaseSens,S)
		local found = false
		for I, W in ipairs(Tab) do
			if S == PerhapsLow(CaseSens,W) then
				S = I
				found = true
				break
			end
		end
		if not found then
			ParamErrorS (ParId, I18nStr (RS.SNotFoundInTab, SIni, table.concat(Tab,', ')))
			S = nil
		end
	end
	return S
end --StrIdxChkTab_Par

function p.RStrIdxChkTab_Par (Args, ParId, Tab, CaseSens)
	if p.Error.yes then return end
	local S = p.StrIdxChkTab_Par (Args, ParId, Tab)
	if p.Error.yes then return end
	if S == nil then
		p.NotAssignedValue (ParId)
	else
		return S
	end
end --RStrIdxChkTab_Par

--Used in parameters to force an integer, number and others to return "NONE" (parameter to non-use)
p.AcceptNone = false 
p.None = "NONE"
local function IsNone (S)
	return p.AcceptNone and (S == p.None)
end	

function p.Num_Par (Args, ParId, Default, LimInf, LimSup)
	if p.Error.yes then return end
	local N = p.Str_Par (Args, ParId)
	if p.Error.yes then return end
	if IsNone(N) then return p.None end
	if N == nil then
		if Default ~= nil then
			N = Default
		end
		return N, true
	else
		p.CheckSIsNum (N, ParId, LimInf, LimSup)
		if p.Error.yes then return end
		return tonumber(N), false
	end
end --Num_Par

function p.RNum_Par (Args, ParId, LimInf, LimSup)
	if p.Error.yes then return end
	local N = p.Num_Par (Args, ParId, nil, LimInf, LimSup)
	if p.Error.yes then return end
	if N == nil then
		p.NotAssignedValue (ParId)
	else
		return N, false
	end
end --RNum_Par

function p.PosNum_Par (Args, ParId, Default, LimInf, LimSup)
	if p.Error.yes then return end
	local N, IsDefault = p.Num_Par (Args, ParId, Default, LimInf, LimSup)
	if IsNone(N) then return p.None end
	if p.Error.yes then return end
	if not IsDefault then
		p.CheckNumIsPos (N, ParId)
		if p.Error.yes then return end
	end
	return N
end --PosNum_Par

function p.RPosNum_Par (Args, ParId, LimInf, LimSup)
	if p.Error.yes then return end
	local N = p.PosNum_Par (Args, ParId, nil, LimInf, LimSup)
	if p.Error.yes then return end
	if N == nil then
		p.NotAssignedValue (ParId)
	else
		return N
	end
end --RNum_Par

function p.ZeroOrPosNum_Par (Args, ParId, Default, LimSup)
	if p.Error.yes then return end
	local N, IsDefault = p.Num_Par (Args, ParId, Default, 0, LimSup)
	if IsNone(N) then return p.None end
	if p.Error.yes then return end
	if not IsDefault then
		p.CheckNumIsZeroOrPos (N, ParId)
		if p.Error.yes then return end
	end
	return N
end --ZeroOrPosNum_Par

function p.RZeroOrPosNum_Par (Args, ParId, LimSup)
	if p.Error.yes then return end
	local N = p.ZeroOrPosNum_Par (Args, ParId, nil, LimSup)
	if p.Error.yes then return end
	if N == nil then
		p.NotAssignedValue (ParId)
	else
		return N
	end
end --RZeroOrPosNum_Par

function p.Int_Par (Args, ParId, Default, LimInf, LimSup)
	N, IsDefault = p.Num_Par (Args, ParId, Default, LimInf, LimSup)
	if IsNone(N) then return p.None end
	if p.Error.yes then return end
	if not IsDefault then
		p.CheckNumIsInt (N, ParId)
		if p.Error.yes then return end
	end
	return N
end --Int_Par

function p.RInt_Par (Args, ParId, LimInf, LimSup)
	if p.Error.yes then return end
	local N = p.Int_Par (Args, ParId, nil, LimInf, LimSup)
	if p.Error.yes then return end
	if N == nil then
		p.NotAssignedValue (ParId)
	else
		return N, true
	end
end --RInt_Par

function p.PosInt_Par (Args, ParId, Default, LimInf, LimSup)
	if p.Error.yes then return end
	local N = p.Int_Par (Args, ParId, Default, LimInf, LimSup)
	if IsNone(N) then return p.None end
	if p.Error.yes then return end
	if N ~= nil then
		p.CheckNumIsPos (N, ParId)
		if p.Error.yes then return end
	end
	return N
end --PosInt_Par

function p.RPosInt_Par (Args, ParId, LimInf, LimSup)
	if p.Error.yes then return end
	local N = p.PosInt_Par (Args, ParId, nil, LimInf, LimSup)
	if p.Error.yes then return end
	if N == nil then
		p.NotAssignedValue (ParId)
	else
		return N, false
	end
end --RPosInt_Par

function p.ZeroOrPosInt_Par (Args, ParId, Default, LimSup)
	if p.Error.yes then return end
	local N = p.Int_Par (Args, ParId, Default, 0, LimSup)
	if IsNone(N) then return p.None end
	if p.Error.yes then return end
	if N ~= nil then
		p.CheckNumIsZeroOrPos (N, ParId)
		if p.Error.yes then return end
	end
	return N
end --ZeroOrPosInt_Par

function p.RZeroOrPosInt_Par (Args, ParId, LimSup)
	if p.Error.yes then return end
	local N = p.ZeroOrPosInt_Par (Args, ParId, nil, LimSup)
	if p.Error.yes then return end
	if N == nil then
		p.NotAssignedValue (ParId)
	else
		return N, true
	end
end --RZeroOrPosInt_Par

---

local function PossibleValues (tab)
	local S = ''
	for _, W in ipairs(tab) do
		if S ~='' then
			S = S..', '
		end	
		local tab = {}
		tab2 = p.I18nParamsTabCS (i18n[W])
		S = S..table.concat (tab2, p.ParaSep)
	end
	return I18nStr (RS.PossibleValues, S)
end --PossibleValues	

---

function p.CheckSize (CurCol, width) --used in XCols
	local val = string.match (width, '%d[%d.,]*')
	if val == nil then
		ParamErrorS (CurCol, I18nStr (RS.SizeWithoutNumber))
	else
		local unit = string.sub (width, #val+1)
		if unit == '' then
			ParamErrorS (CurCol, I18nStr (RS.SizeUnitRequired))
		elseif (unit ~= '%') and (unit ~= 'em') and (unit ~= 'px') then
			ParamErrorS (CurCol, I18nStr (RS.InvalSizeUnit, unit))
		end
	end	
end --CheckSize	

function p.Size_Par (Args, ParId, WithPerc, limits, Default)
	if p.Error.yes then return end
	local width = p.Str_Par (Args, ParId, Default)
	if p.Error.yes then return end
	if p.HasValue (width) then
		local val = string.match (width, '%d[%d.,]*')
		if val == nil then
			ParamErrorS (ParId, I18nStr (RS.SizeWithoutNumber))
		else
			local unit = string.sub (width, #val+1)
			if unit == '' then
				ParamErrorS (ParId, I18nStr (RS.SizeUnitRequired))
			elseif (not WithPerc) and (unit == '%') then	
				ParamErrorS (ParId, I18nStr (RS.InvalSizeUnit, unit))
			elseif (unit ~= '%') and (unit ~= 'em') and (unit ~= 'px') then
				ParamErrorS (ParId, I18nStr (RS.InvalSizeUnit, unit))
			elseif limits ~= nil then
				checked = false
				val = tonumber (val)
				function Check (sel_lim, sel_unit)
					if (not checked) and (sel_lim ~= nil) and (unit == sel_unit) then
						if ((sel_lim[1] ~= nil) and (val < sel_lim[1])) or ((sel_lim[2] ~= nil) and (val > sel_lim[2])) then
							ParamErrorS (ParId, I18nStr (RS.NIsNotInRange, width, sel_lim[1]..sel_unit, sel_lim[2]..sel_unit))
						end	
						checked = true
					end	
				end	
				Check (limits['em'], 'em')
				Check (limits['px'], 'px')
				if WithPerc then
					Check (limits['perc'], '%')
				end	
			end
		end
	end
	return width
end	--Size_Par

function p.RSize_Par (Args, ParId, WithPerc, limits)
	if p.Error.yes then return end
	local width = p.Size_Par (Args, ParId, WithPerc, limits)
	if p.Error.yes then return end
	if width == nil then
		p.NotAssignedValue (ParId)
	else
		return width
	end
end --RSize_Par

p.Colors = {
	-- Pink colors
	["pink"]			= 'FFC0CB', -- Pink
	["lightpink"]		= 'FFB6C1', -- LightPink
	["hotpink"]			= 'FF69B4', -- HotPink
	["deeppink"]		= 'FF1493', -- DeepPink
	["palevioletred"] 	= 'DB7093', -- PaleVioletRed
	["mediumvioletred"] = 'C71585', -- MediumVioletRed
	-- Red colors
	["lightsalmon"]		= 'FFA07A', -- LightSalmon
	["salmon"]			= 'FA8072', -- Salmon
	["darksalmon"]		= 'E9967A', -- DarkSalmon
	["lightcoral"]		= 'F08080', -- LightCoral
	["indianred"]		= 'CD5C5C', -- IndianRed
	["crimson"]			= 'DC143C', -- Crimson
	["firebrick"]		= 'B22222', -- Firebrick
	["darkred"]			= '8B0000', -- DarkRed
	["red"]				= 'FF0000', -- Red
	-- Orange colors
	["orangered"]		= 'FF4500', -- OrangeRed
	["tomato"]			= 'FF6347', -- Tomato
	["coral"]			= 'FF7F50', -- Coral
	["darkorange"]		= 'FF8C00', -- DarkOrange
	["orange"]			= 'FFA500', -- Orange
	-- Yellow colors
	["yellow"]			= 'FFFF00', -- Yellow
	["lightyellow"]		= 'FFFFE0', -- LightYellow
	["lemonchiffon"]	= 'FFFACD', -- LemonChiffon
	["lightgoldenrodyellow"] = 'FAFAD2', -- LightGoldenrodYellow
	["papayawhip"]		= 'FFEFD5', -- PapayaWhip
	["moccasin"]		= 'FFE4B5', -- Moccasin
	["peachpuff"]		= 'FFDAB9', -- PeachPuff
	["palegoldenrod"]	= 'EEE8AA', -- PaleGoldenrod
	["khaki"]			= 'F0E68C', -- Khaki
	["darkkhaki"]		= 'BDB76B', -- DarkKhaki
	["gold"]			= 'FFD700', -- Gold
	-- Brown colors
	["cornsilk"]		= 'FFF8DC', -- Cornsilk
	["blanchedalmond"]	= 'FFEBCD', -- BlanchedAlmond
	["bisque"]			= 'FFE4C4', -- Bisque
	["navajowhite"]		= 'FFDEAD', -- NavajoWhite
	["wheat"]			= 'F5DEB3', -- Wheat
	["burlywood"]		= 'DEB887', -- Burlywood
	["tan"]				= 'D2B48C', -- Tan
	["rosybrown"]		= 'BC8F8F', -- RosyBrown
	["sandybrown"]		= 'F4A460', -- SandyBrown
	["goldenrod"]		= 'DAA520', -- Goldenrod
	["darkgoldenrod"]	= 'B8860B', -- DarkGoldenrod
	["peru"]			= 'CD853F', -- Peru
	["chocolate"]		= 'D2691E', -- Chocolate
	["saddlebrown"]		= '8B4513', -- SaddleBrown
	["sienna"]			= 'A0522D', -- Sienna
	["brown"]			= 'A52A2A', -- Brown
	["maroon"]			= '800000', -- Maroon
	-- Green colors
	["darkolivegreen"]	= '556B2F', -- DarkOliveGreen
	["olive"]			= '808000', -- Olive
	["olivedrab"]		= '6B8E23', -- OliveDrab
	["yellowgreen"]		= '9ACD32', -- YellowGreen
	["limegreen"]		= '32CD32', -- LimeGreen
	["lime"]			= '00FF00', -- Lime
	["lawngreen"]		= '7CFC00', -- LawnGreen
	["chartreuse"]		= '7FFF00', -- Chartreuse
	["greenyellow"]		= 'ADFF2F', -- GreenYellow
	["springgreen"]		= '00FF7F', -- SpringGreen
	["mediumspringgreen"] = '00FA9A', -- MediumSpringGreen
	["lightgreen"]		= '90EE90', -- LightGreen
	["palegreen"]		= '98FB98', -- PaleGreen
	["darkseagreen"]	= '8FBC8F', -- DarkSeaGreen
	["mediumaquamarine"] = '66CDAA', -- MediumAquamarine
	["mediumseagreen"]	= '3CB371', -- MediumSeaGreen
	["seagreen"]		= '2E8B57', -- SeaGreen
	["forestgreen"]		= '228B22', -- ForestGreen
	["green"]			= '008000', -- Green
	["darkgreen"]		= '006400', -- DarkGreen
	-- Cyan colors
	["aqua"]			= '00FFFF', -- Aqua
	["cyan"]			= '00FFFF', -- Cyan
	["lightcyan"]		= 'E0FFFF', -- LightCyan
	["paleturquoise"]	= 'AFEEEE', -- PaleTurquoise
	["aquamarine"]		= '7FFFD4', -- Aquamarine
	["turquoise"]		= '40E0D0', -- Turquoise
	["mediumturquoise"]	= '48D1CC', -- MediumTurquoise
	["darkturquoise"]	= '00CED1', -- DarkTurquoise
	["lightseagreen"]	= '20B2AA', -- LightSeaGreen
	["cadetblue"]		= '5F9EA0', -- CadetBlue
	["darkcyan"]		= '008B8B', -- DarkCyan
	["teal"]			= '008080', -- Teal
	-- Blue colors
	["lightsteelblue"]	= 'B0C4DE', -- LightSteelBlue
	["powderblue"]		= 'B0E0E6', -- PowderBlue
	["lightblue"]		= 'ADD8E6', -- LightBlue
	["skyblue"]			= '87CEEB', -- SkyBlue
	["lightskyblue"]	= '87CEFA', -- LightSkyBlue
	["deepskyblue"]		= '00BFFF', -- DeepSkyBlue
	["dodgerblue"]		= '1E90FF', -- DodgerBlue
	["cornflowerblue"]	= '6495ED', -- CornflowerBlue
	["steelblue"]		= '4682B4', -- SteelBlue
	["royalblue"]		= '4169E1', -- RoyalBlue
	["blue"]			= '0000FF', -- Blue
	["mediumblue"]		= '0000CD', -- MediumBlue
	["darkblue"]		= '00008B', -- DarkBlue
	["navy"]			= '000080', -- Navy
	["midnightblue"]	= '191970', -- MidnightBlue
	-- Purple, violet, and magenta colors
	["lavender"]		= 'E6E6FA', -- Lavender
	["thistle"]			= 'D8BFD8', -- Thistle
	["plum"]			= 'DDA0DD', -- Plum
	["violet"]			= 'EE82EE', -- Violet
	["orchid"]			= 'DA70D6', -- Orchid
	["fuchsia"]			= 'FF00FF', -- Fuchsia
	["magenta"]			= 'FF00FF', -- Magenta
	["mediumorchid"]	= 'BA55D3', -- MediumOrchid
	["mediumpurple"]	= '9370DB', -- MediumPurple
	["blueviolet"]		= '8A2BE2', -- BlueViolet
	["darkviolet"]		= '9400D3', -- DarkViolet
	["darkorchid"]		= '9932CC', -- DarkOrchid
	["darkmagenta"]		= '8B008B', -- DarkMagenta
	["purple"]			= '800080', -- Purple
	["indigo"]			= '4B0082', -- Indigo
	["darkslateblue"]	= '483D8B', -- DarkSlateBlue
	["slateblue"]		= '6A5ACD', -- SlateBlue
	["mediumslateblue"]	= '7B68EE', -- MediumSlateBlue
	-- White colors
	["white"]			= 'FFFFFF', -- White
	["snow"]			= 'FFFAFA', -- Snow
	["honeydew"]		= 'F0FFF0', -- Honeydew
	["mintcream"]		= 'F5FFFA', -- MintCream
	["azure"]			= 'F0FFFF', -- Azure
	["aliceblue"]		= 'F0F8FF', -- AliceBlue
	["ghostwhite"]		= 'F8F8FF', -- GhostWhite
	["whitesmoke"]		= 'F5F5F5', -- WhiteSmoke
	["seashell"]		= 'FFF5EE', -- Seashell
	["beige"]			= 'F5F5DC', -- Beige
	["oldlace"]			= 'FDF5E6', -- OldLace
	["floralwhite"]		= 'FFFAF0', -- FloralWhite
	["ivory"]			= 'FFFFF0', -- Ivory
	["antiquewhite"]	= 'FAEBD7', -- AntiqueWhite
	["linen"]			= 'FAF0E6', -- Linen
	["lavenderblush"]	= 'FFF0F5', -- LavenderBlush
	["mistyrose"]		= 'FFE4E1', -- MistyRose
	-- Gray and black colors
	["gainsboro"]		= 'DCDCDC', -- Gainsboro
	["lightgray"]		= 'D3D3D3', -- LightGray
	["silver"]			= 'C0C0C0', -- Silver
	["darkgray"]		= 'A9A9A9', -- DarkGray
	["gray"]			= '808080', -- Gray
	["dimgray"]			= '696969', -- DimGray
	["lightslategray"]	= '778899', -- LightSlateGray
	["slategray"]		= '708090', -- SlateGray
	["darkslategray"]	= '2F4F4F', -- DarkSlateGray
	["black"]			= '000000', -- Black
}

function p.ColorToHex (frame)
    local args = p.GetArgs (frame)
	return p.RColor_Par (args, 1)
end	

local function GetRValue (rgb)
  -- Red color
  return tonumber (string.sub(rgb,1,2), 16)
end

local function GetGValue(rgb)
  -- Green color	
  return tonumber (string.sub(rgb,3,4), 16)
end

local function GetBValue(rgb)
  -- BLue color	
  return tonumber (string.sub(rgb,5,6), 16)
end

function p.CheckSIsColor (S, ParId, Default)
	-- Returns if is valid and the color in format NNNNNN i.e. 
	--   for S == 'Green' returns true,'008000'
	--   for S == '#008000' returns true,'008000'
	function SixCharsFromCol (Col)
		if Col == nil then
			return nil
		else
			function NumColor (Col)
				if string.len (Col) ~= 6 then
					ParamError (ParId, RS.InvalColorLength, Col)
				end	
				if tonumber ('0x'..Col) == nil then
					ParamError (ParId, RS.InvalColorChar, Col)
				end	
			end	
			if string.sub (Col, 1, 5) == '&#35;' then
				Col = string.sub (Col, 6)
				NumColor (Col)
			elseif string.sub (Col, 1, 1) == '#' then
				Col = string.sub (Col, 2)
				NumColor (Col)
			else
				local ColI = Col
				local rgbn = string.lower (Col)
				Col = p.Colors[rgbn] 
				if Col == nil then
			  		ParamError (ParId, RS.InvalColorName, ColI)
				end
			end	
			if p.Error.yes then
				return nil
			else
				return Col
			end	
		end	
	end	-- SixCharsFromCol
	if p.Error.yes then return end
	if S == nil then
		S = Default
		return SixCharsFromCol (S) 
	end
	if S == nil then
		return nil
	else
		return SixCharsFromCol (S) 
	end	
end --CheckSIsColor

function p.Color_Par (Args, ParId, Default)
	if p.Error.yes then return end
	local rgb = p.Str_Par (Args, ParId)
	if p.Error.yes then return end
	return p.CheckSIsColor (rgb, ParId, Default)
end	--Color_Par

function p.RColor_Par (Args, ParId, Default)
	if p.Error.yes then return end
	local rgb = p.RStr_Par (Args, ParId)
	if p.Error.yes then return end
	return p.CheckSIsColor (rgb, ParId)
end	--Color_Par

---

function p.HAlign_Par (Args, ParId, Default)
	local HAlign = {
		RS.left,
		RS.right,
		RS.center,
	}
	if p.Error.yes then return end
	local align = p.Str_Par (Args, ParId)
	if p.Error.yes then return end
	if (align == nil) or (align == '') then
		return Default
	else	
		local Idx = p.IdxFromWdTab (i18n, align, true, HAlign)
		if Idx == 0 then
			ParamErrorS (ParId, I18nStr (RS.InvalAlign,align)..'. '..PossibleValues(HAlign))
		else
			return HAlign[Idx]
		end
	end
end --HAlign_Par

function p.RHAlign_Par (Args, ParId)
	if p.Error.yes then return end
	local align = p.HAlign_Par (Args, ParId)
	if p.Error.yes then return end
	if align == nil then
		p.NotAssignedValue (ParId)
	else
		return align
	end
end --RHAlign_Par

---

function p.VAlign_Par (Args, ParId, Default)
	local VAlign = {
		RS.top,
		RS.bottom,
		RS.center,
	}
	if p.Error.yes then return end
	local align = p.Str_Par (Args, ParId)
	if p.Error.yes then return end
	if (align == nil) or (align == '') then
		return Default
	else	
		local Idx = p.IdxFromWdTab (i18n, align, true, VAlign)
		if Idx == 0 then
			ParamErrorS (ParId, I18nStr (RS.InvalAlign,align)..'. '..PossibleValues(VAlign))
		else
			return VAlign[Idx]
		end
	end
end --VAlign_Par

function p.RVAlign_Par (Args, ParId)
	if p.Error.yes then return end
	local align = p.VAlign_Par (Args, ParId)
	if p.Error.yes then return end
	if align == nil then
		p.NotAssignedValue (ParId)
	else
		return align
	end
end --RVAlign_Par

---

function p.Bool_Par (Args, ParId, Default)
	local yesno = {
		RS.Yes,
		RS.No,
	}
	if p.Error.yes then return end
	local B = p.Str_Par (Args, ParId)
	if IsNone(N) then return p.None end
	if p.Error.yes then return end
	if (B == nil) or (B == '') then
		return Default
	else
		local Idx = p.IdxFromWdTab (i18n, B, true, yesno)
		if Idx == 0 then
			ParamErrorS (ParId, I18nStr (RS.InvalBool,B)..'. '..PossibleValues(yesno))
		elseif Idx == 1 then
			return true
		elseif Idx == 2 then	
			return false
		end
	end
end --Bool_Par

function p.RBool_Par (Args, ParId)
	if p.Error.yes then return end
	local B = p.Bool_Par (Args, ParId)
	if p.Error.yes then return end
	if B == nil then
		p.NotAssignedValue (ParId)
	else
		return B
	end
end --RBool_Par

---

function SFoundInArr (val, CaseSens, ParId)
	found = false
	if type(ParId) == 'table' then
		for _, W in ipairs(ParId) do
			if val == PerhapsLow(CaseSens, W) then
				found = true
				break
			end
		end
	else
		if val == PerhapsLow(CaseSens, ParId) then
			found = true
		end
	end
	return found
end --SFoundInArr

local function StrIdxChkTab0 (val, CaseSens, Default, ...)
	if p.Error.yes then return end
	if arg == nil then
		error('Not parameters trying to find "'..W..'"') --It doesn't require translation, only for degug
	end
	local Idx = 0
	if not p.Error.yes then 
		if not CaseSens then
			val = string.lower(val)
		end
		local tab = unpack(arg)
		for I, W in ipairs(tab) do
			if SFoundInArr (val, CaseSens, W) then
				Idx = I
				break
			end
		end
		if (Idx == 0) and (Default ~= nil) then
			Idx = Default
		end
	end
	if p.Error.yes then 
		return p.MsgError()
	else
		return Idx
	end
end --StrIdxChkTab0

function p.StrIdxChkTab (Args, ParId, CaseSens, Default, ...)
	if p.Error.yes then return end
	local W = p.Str_Par (Args, ParId, Default)
	local Idx = StrIdxChkTab0 (W, CaseSens, Default, arg)
	if p.Error.yes then 
		return p.MsgError()
	else
		return Idx
	end
end--StrIdxChkTab
	
function p.RStrIdxChkTab (Args, ParId, CaseSens, ...)
	if p.Error.yes then return end
	local W = p.RStr_Par (Args, ParId)
	local Idx = StrIdxChkTab0 (W, CaseSens, nil, arg)
	if p.Error.yes then 
		return p.MsgError()
	else
		return Idx
	end
end --RStrIdxChkTab

local function IdxOrNotFound (ParId, W, Idx, ...)
	local Err = {}
	if Idx == 0 then
		local tab = unpack(arg)
		for _, Wd in ipairs(tab) do
			if type(Wd) == 'table' then
				table.insert (Err, table.concat(Wd,p.ParaSep))
			else
				table.insert (Err, Wd)
			end
		end
		ParamErrorS (ParId, I18nStr (RS.SNotFoundInTab, W, table.concat(Err,', ')))
	else
		return Idx
	end
end --IdxOrNotFound	

function p.StrIdxChkTabE (Args, ParId, CaseSens, Default, ...)
	if p.Error.yes then return end
	local W = p.Str_Par (Args, ParId, Default)
	local Idx = StrIdxChkTab0 (W, CaseSens, Default, arg)
	if p.Error.yes then 
		return p.MsgError()
	else
		return IdxOrNotFound (ParId, W, Idx, arg)
	end
end--StrIdxChkTabE
	
function p.RStrIdxChkTabE (Args, ParId, CaseSens, ...)
	if p.Error.yes then return end
	local W = p.RStr_Par (Args, ParId)
	local Idx = StrIdxChkTab0 (W, CaseSens, nil, arg)
	if p.Error.yes then 
		return p.MsgError()
	else
		return IdxOrNotFound (ParId, W, Idx, arg)
	end
end --RStrIdxChkTabE

-----

local function InRange (Num, ParId, MinItemNum, MaxItemNum)
	out = ((MinItemNum ~= nil) and (Num < MinItemNum)) or ((MaxItemNum ~= nil) and (Num > MaxItemNum))
	if out then
		if MaxItemNum ~= nil then
			ParamErrorS (ParId, I18nStr (RS.STabIsNotInRange, Num, tostring(MinItemNum), tostring(MaxItemNum)))
		else
			ParamErrorS (ParId, I18nStr (RS.STabFewItems, Num, tostring(MinItemNum)))
		end
	end
	return not out
end--InRange

function p.StrTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	if Sep == nil then
		error ('Undefined items separator') -- Don't translate, it ies only for debug
	end
	if OnEmpty == nil then
		OnEmpty = 2  --set an empty value error
	end
	local tab = mw.text.split (p.Str_Par (Args, ParId), Sep)
	if p.Error.yes then return end
	if InRange (#tab, ParId, MinItemNum, MaxItemNum) then
		for I, W in ipairs(tab) do
			tab[I] = mw.text.trim (tab[I])
		end
		if OnEmpty == 0 then
			return tab
		elseif OnEmpty == 1 then
			local tab2 = {} 
			for _, W in ipairs(tab) do
				if W ~= '' then
					table.insert (tab2, W)
				end
			end
			return tab2
		else
			for _, W in ipairs(tab) do
				if W == '' then
					ParamError (ParId, RS.EmptyValue)
				end
			end
			return tab
		end
	end
end --StrTab_1Par

function p.NumTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	tab = p.StrTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, OnEmpty)
	if p.Error.yes then return end
	for I, W in ipairs(tab) do
		if W ~= nil then
			local J = tonumber(W)
			if not J then
				ParamError (ParId, RS.SIsNotNumber, W)
				return tab
			else
				p.CheckNum (J, Pos, LimInf, LimSup)
				if p.Error.yes then return end
				tab[I] = J
			end
		end
	end
	return tab
end --NumTab_1Par	

function p.PosNumTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	tab = p.NumTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsPos (W, ParId)
		end
		if p.Error.yes then return end
	end
	return tab
end --ZeroOrPosNumTab_1Par	

function p.ZeroOrPosNumTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	tab = p.NumTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsZeroOrPos (W, ParId)
		end
		if p.Error.yes then return end
	end
	return tab
end --ZeroOrPosNumTab_1Par	

function p.IntTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	tab = p.NumTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsInt (W, ParId)
		end
		if p.Error.yes then return end
	end
	return tab
end--IntTab_1Par

function p.PosIntTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	tab = p.IntTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsPos (W, ParId)
		end
		if p.Error.yes then return end
	end
	return tab
end --PosIntTab_1Par

function p.ZeroOrPosIntTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	tab = p.IntTab_1Par (Args, ParId, Sep, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsZeroOrPos (W, ParId)
		end
		if p.Error.yes then return end
	end
	return tab
end --ZeroOrPosIntTab_1Par

--------

function p.StrTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, OnEmpty)
	local tab = {}
	if OnEmpty == nil then
		OnEmpty = 2  --set an empty value error
	end
	function IncInTab (val)
		val = mw.text.trim(val)
		if OnEmpty == 0 then
	 		if val ~= '' then
				table.insert(tab,val)
			end
		elseif OnEmpty == 1 then
	 		table.insert(tab,val)
		else
			if val == '' then
				ParamError (ParId, RS.EmptyValue)
			else	
	 			table.insert(tab,val)
			end
	 	end
	end
	if p.Error.yes then return end
	if type(ParId) == 'number' then
		if ParId == 1 then
			for _, v in ipairs(Args) do
				IncInTab (v)
				if p.Error.yes then return end
			end
		else	
			for k = ParId, NArgs do
				local S = Args[k]
				IncInTab (S)
				if p.Error.yes then return end
			end
		end	
	elseif type(ParId) == 'string' then
		found = false
		local Pos = 1
		while not found do
			local key = string.format (ParId, Pos)
			for k, v in pairs(Args) do
				if k == key then
					IncInTab (v)
					if p.Error.yes then return end
					found = true
					break
				end
			end
			if not found then 
				break
			end
			found = false
			Pos = Pos + 1
		end
	elseif type(ParId) == 'table' then
		found = false
		local Pos = 1
		while not found do
			for _, b in ipairs(ParId) do
				local key = string.format (b, Pos)
				for k, v in pairs(Args) do
					if k == key then
						IncInTab (v)
						if p.Error.yes then return end
						found = true
						break
					end
				end
				if found then
					break
				end
			end
			if not found then 
				break
			end
			found = false
			Pos = Pos + 1
		end
	end
	InRange (#tab, ParId, MinItemNum, MaxItemNum)
	return tab, #tab
end --StrTab_NPar

function p.NumTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local tab = p.StrTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, OnEmpty)
	if p.Error.yes then return end
	local Pos = ParId
	for I, W in ipairs(tab) do
		if W ~= nil then
			local J = tonumber(W)
			if not J then
				ParamError (Pos, RS.SIsNotNumber, W)
			else
				p.CheckNum (J, Pos, LimInf, LimSup)
				if p.Error.yes then return end
				tab[I] = J
			end
		end
		Pos = Pos + 1
	end
	return tab
end --NumTab_NPar

function p.PosNumTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local tab = p.NumTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local Pos = ParId
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsPos (W, Pos) 
		end
		if p.Error.yes then return end
		Pos = Pos + 1
	end
	return tab
end --PosNumTab_NPar

function p.ZeroOrPosNumTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local tab = p.NumTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local Pos = ParId
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsZeroOrPos (W, Pos) 
		end
		if p.Error.yes then return end
		Pos = Pos + 1
	end
	return tab
end --ZeroOrPosNumTab_NPar

function p.IntTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local tab = p.NumTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local Pos = ParId
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckSIsInt (W, Pos) 
		end
		if p.Error.yes then return end
		Pos = Pos + 1
	end
	return tab
end --IntTab_NPar

function p.PosIntTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local tab = p.IntTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local Pos = ParId
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsPos (W, Pos) 
		end
		if p.Error.yes then return end
		Pos = Pos + 1
	end
	return tab
end --PosIntTab_NPar

function p.ZeroOrPosIntTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local tab = p.IntTab_NPar (Args, NArgs, ParId, MinItemNum, MaxItemNum, LimInf, LimSup, OnEmpty)
	if p.Error.yes then return end
	local Pos = ParId
	for _, W in ipairs(tab) do
		if W ~= nil then
			p.CheckNumIsZeroOrPos (W, Pos)
		end
		if p.Error.yes then return end
		Pos = Pos + 1
	end
	return tab
end --ZeroOrPosIntTab_NPar

----------

function p.TemplateName(frame)
	local S = frame:getParent():getTitle()
	return string.sub(S, string.find(S,':')+1)
end	

function p.MainTemplateName(frame)
	local S = p.TemplateName(frame)
	return string.sub (S, 1, #S - S:reverse():find("/"))
end	

local function CheckIsStrNotEmpty (v, S)
	if v == '' then
		error ('"'..S..'" has not assigned value') -- Translation not required
	else	
		return v
	end	
end --CheckIsStrNotEmpty

function p.CheckIsStr (v, S)
	if type(v) == "string" then
		return CheckIsStrNotEmpty (v, S)
	elseif type(v) == nil then
		error ('Not found item for "'..S..'"') -- Translation not required
	else	
		SD.vtos (v)
		error ('"'..SD.s..'" (for "'..S..'") is not a string') -- Translation not required
	end	
end --CheckIsStr

local function WhenNoStrOrTab (v, S)
	SD.vtos (v)
	error ('"'..SD.s..'" (for "'..S..'") is not a string/table') -- Translation not required
end

function p.CheckIsStrOrTab (v, S)
	--It does not allow empty strings
	if type(v) == "string" then
		return CheckIsStrNotEmpty (v, S)
	elseif type(v) == "table" then
		for _, vv in ipairs(v) do
			if type(vv) == "string" then
				CheckIsStrNotEmpty (vv, S)
			end	
		end	
		return v
	else
		WhenNoStrOrTab (v, S)
	end	
end --CheckIsStrOrTab

function p.CheckIsAnyStrOrTab (v, S)
	--It allows empty strings
	if (type(v) == "string") or (type(v) == "table") then
		return v
	else	
		WhenNoStrOrTab (v, S)
	end	
end --CheckIsAnyStrOrTab

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

function p.rgbToHex(rgb) --not used
	local hexadecimal = '#'
	for key, value in ipairs(rgb) do
		local hex = ''
		if (value < 0) or (value > 255) then
			error ('Invalid color number: '..value)
		end	
		while value > 0 do
			local index = math.fmod(value, 16) + 1
			value = math.floor (value/16)
			hex = string.sub('0123456789ABCDEF', index, index) .. hex			
		end
		if string.len(hex) == 0 then
			hex = '00'
		elseif string.len(hex) == 1 then
			hex = '0' .. hex
		end
		hexadecimal = hexadecimal .. hex
	end
	return hexadecimal
end --p.rgbToHex

function p._ReverseColor0 (rgb) --not used
  return p.rgbToHex ({255-GetRValue(rgb), 255-GetGValue(rgb), 255-GetBValue(rgb)});
end

function p._ReverseColor (rgb)
	-- rgb is a string with 6 characters in format NNNNNN i.e. '008000'
	-- (Better contrast than _ReverseColor0 for some colors: i.g. grey)
	local BreakGrey = 150
	local BreakColor = 180
	local RValue = GetRValue (rgb)
	local BValue = GetBValue (rgb)
	local GValue = GetGValue (rgb)
	if     (RValue < BreakGrey) and (GValue < BreakGrey) and  (BValue < BreakGrey)	then  
		return 'White'
	elseif (RValue >= BreakGrey) and (GValue >= BreakGrey) and  (BValue >= BreakGrey) then
		return 'Black'
	elseif (RValue > BreakColor) and (GValue > BreakColor)	then
		return 'Blue'
	elseif (RValue > BreakColor) and (BValue > BreakColor)	then
		return 'Green'
	elseif (GValue > BreakColor) and  (BValue > BreakColor)	then
		return 'Red'
	elseif (RValue > BreakColor) then
		return 'Aqua' --Blue + Green
	elseif (GValue > BreakColor) then
		return 'Fuchsia' --Red + Blue
	elseif (BValue > BreakColor) then
		return 'Yellow' --Red + Green
	else  
		return 'White'	  
	end
end --_ReverseColor

function p.ReverseColor (frame)
    local args = p.GetArgs (frame)
	local color = p.RColor_Par (args, 1)
	return p._ReverseColor (color)
end --ReverseColor

function p._color_black_contrast (color)
	local colors = {
		GetRValue(color), 
		GetGValue(color), 
		GetBValue(color), 
	}
	local dif = math.max (colors[1], colors[2], colors[3]) - math.min (colors[1], colors[2], colors[3])
	local val = 0.28*colors[1] + 0.64*colors[2] + 0.08*colors[3]
	local to_subtract = math.abs(dif-255)
	local to_dif = 128-math.abs(val-128)
	local to_subtract2 = to_subtract * to_dif * 0.002 
	local res = math.floor ((val - to_subtract2) + 0.5)
	return res
end --_color_black_contrast

function p.color_black_contrast (frame)
	-- Determine the luminosity of a color, with some corrections for grays, 
	-- for the background of an overlay text, in part from:
	-- https://www.johndcook.com/blog/2009/08/24/algorithms-convert-color-grayscale/
	local args = p.GetArgs (frame)
	local color = p.RColor_Par (args, 1)
	return p._color_black_contrast (color)
end --color_black_contrast

function p._txtcolor_for_bg (rgb)
	if p._color_black_contrast (rgb) < 90 then
		return 'white'
	else
		return 'black'
	end	
end --_txtcolor_for_bg

function p.txtcolor_for_bg (frame)
	-- For better readability, determine whether the color of the letters 
	-- should be white or black 
	-- depending on the background color.
    local args = p.GetArgs (frame)
	local color = p.RColor_Par (args, 1)
	return p._txtcolor_for_bg (color)
end --txtcolor_for_bg

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

return p