Moduuli:ValidateInfobox


require('Moduuli:Magic-escape')

local validator = {}

function validator.IUCN(frame)
    local new_args = validator._getParameters( frame.args, {'status', 'tunnus'} );
    local iucn= mw.ustring.lower(new_args['status'] or '');
    local tunnus= new_args['tunnus'] ;
    
    if (iucn == "") then
    	return "";
    end

    if (iucn == "elinvoimainen") then iucn_wd="Q211005";
    elseif (iucn=="silmälläpidettävä") then iucn_wd="Q719675";
	elseif (iucn=="vaarantunut") then iucn_wd="Q278113";
    elseif (iucn=="erittäin uhanalainen") then iucn_wd="Q11394";
    elseif (iucn=="äärimmäisen uhanalainen") then iucn_wd="Q219127";
    elseif (iucn=="luonnosta hävinnyt" ) then iucn_wd="Q239509";
    elseif (iucn=="puutteellisesti tunnettu" ) then iucn_wd="Q3245245";
    elseif (iucn=="hävinnyt" ) then iucn_wd="Q237350";
    elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="elinvoimainen"}) ) then iucn_wd="Q211005";
    elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="silmälläpidettävä"}) ) then iucn_wd="Q719675";
	elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="vaarantunut"}) ) then iucn_wd="Q278113";
    elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="erittäin uhanalainen"}) ) then iucn_wd="Q11394";
    elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="äärimmäisen uhanalainen"}) ) then iucn_wd="Q219127";
    elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="luonnosta hävinnyt"}) ) then iucn_wd="Q239509";
    elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="puutteellisesti tunnettu"}) ) then iucn_wd="Q3245245";
    elseif (iucn==mw.ustring.lower(frame:expandTemplate{title="hävinnyt"}) ) then iucn_wd="Q237350";
    else iucn_wd = "FAIL";
    end

	entity = mw.wikibase.getEntityObject();
	if not entity then 
		return ""; 
	end
	
	if not entity.claims then
		return "";
	end

	ok=0;
	wikidata_iucn_claims= entity.claims["P141"];
	if (wikidata_iucn_claims~=nil) then
		for i, statement in pairs( wikidata_iucn_claims  ) do
			if statement.rank == 'preferred' or statement.rank == 'normal' then
					wikidata_iucn="Q" .. statement.mainsnak.datavalue.value['numeric-id'];
					if (wikidata_iucn == iucn_wd) then
				 		return "<span class='wd_tests'>[https://tools.wmflabs.org/fiwiki-tools/testit/".. tunnus .."/OK/IUCN IUCN-testi OK]</span>";
					end
	    	end
		end
		ret="[[Luokka:Taksoboksin status-parametri ei vastaa Wikidatassa olevaa IUCN-arvoa]]";
		ret=ret .. "<span class='wd_tests'>[https://tools.wmflabs.org/fiwiki-tools/testit/" .. tunnus ..  "/FAIL/IUCN Virheellinen IUCN-arvo][[Luokka:IUCN-uhanalaisuusluokitus eroaa Wikidatasta]]</span>";
		return ret;
	else
	    return ""; -- P141 ei ole määritelty Wikidatassa
	end
end



function validator.split(str, pat)
   local t = {}  -- NOTE: use {n = 0} in Lua-5.0
   local fpat = "(.-)" .. pat
   local last_end = 1
   local s, e, cap = str:find(fpat, 1)
   while s do
      if s ~= 1 or cap ~= "" then
	 table.insert(t,cap)
      end
      last_end = e+1
      s, e, cap = str:find(fpat, last_end)
   end
   if last_end <= #str then
      cap = str:sub(last_end)
      table.insert(t, cap)
   end
   return t
end

function validator.trim(s)
  return s:match "^%s*(.-)%s*$"
end

function validator.glue_numbers(s)
    s=mw.text.trim(s)
    s=mw.ustring.gsub(s, "&nbsp;", "");

   	pattern="^(-?[0123456789/–+,.]+)";
    
    local iterator = mw.text.gsplit(s, "%s");
    local out=""
    for w in iterator do
		result = mw.ustring.match( w, pattern, 1 );
		if result ~= nil then
			out=out .. w
		else
			out=out .. " " .. w
		end
    end
	return mw.text.trim(out)
end



function validator.wikidata_name(frame)
    local new_args = validator._getParameters( frame.args, {'nimi', 'tunnus', 'huomioi kirjainkoko'} );
    local input_name_str = new_args['nimi'] or '';
    local tunnus = new_args['tunnus'] or '';    
    local case_sensitive = new_args['huomioi kirjainkoko'] or '';
	local langs = {'fi', 'en', 'de', 'sv', 'no', 'ru'}
	local aliases = {}
	local aliases_debug="";
	local name_debug="";
	local split_debug="";
	local match_postfix="$";
    
	if input_name_str =="" then
		return "";
	end

	if tunnus=="" then
		return "";
	end

	entity = mw.wikibase.getEntityObject()
	if not entity then return nil end
	id = entity.id

	for l, lang in pairs(langs) do
		local tmp_name=entity:getLabel( lang );
		if tmp_name ~= nil then
			if case_sensitive =='' then			
				table.insert( aliases, string.lower(tmp_name) );
			else
				table.insert( aliases, tmp_name );
			end
		end
		if entity.aliases and entity.aliases[lang] ~= nil then
			for i, alias in pairs( entity.aliases[lang] ) do
				if case_sensitive =='' then
					table.insert( aliases, string.lower(alias.value) );
    			else
    				table.insert( aliases, alias.value );
    			end	
			end
		end
	end

	input_name_str= string.gsub (input_name_str, "(<%s*small[^>]*>)", "");
	input_name_str= string.gsub (input_name_str, "(<%s*span[^>]*>)", "");

	local names=validator.split(input_name_str, "\<br\>");
	for k1, name in pairs( names ) do
		if mw.text.trim(name) ~= "" then
			-- Poistetaan viitteet
		    name=mw.text.killMarkers( name );
	    	--Poistetaan kursivoinnit
	    	name=string.gsub( name, "'", "");

	    	if case_sensitive =='' then
	    		name=string.lower(name);
	    	end	
	    	name=mw.text.trim(name);
	    	names[k1]=name;
	   		if k1>1 then
				match_postfix="";
			end
		else
			table.remove(names, k1);
	    end
	end

	
	local ok=1;
	for k, name in pairs(names) do
		part_ok=0;
		for _,wikidata_name in pairs(aliases) do
			if name==wikidata_name then
				f=true
			else
				-- Escapee kaikki erikoismerkit
				wikidata_name=magic_escape(wikidata_name);
				f=(string.find(name,"^" .. wikidata_name .. match_postfix ) or name==wikidata_name);
			end
			-- Tarkista onko ylläolevan ehdon vuoksi f aina true/false eikä ikinä nil.
		    if (f ~= nil) and f then
		    	part_ok=1;
		    	break;
	    	end
		end

		-- Jos tulee yksikin osa joka ei läpäise testiä, niin luovutetaan.
		if part_ok==0 then
			ok=0;
			break;
		end
	end
   

	if ok == 1 then
		return "<span class='wd_tests'>[https://tools.wmflabs.org/fiwiki-tools/testit/".. tunnus .."/OK/NIMI Nimi-testi OK]</span>";
	else
		ret="[[Luokka:Tietolaatikon nimi-parametri ei vastaa Wikidatassa olevaa nimeä]]";
		ret=ret .. "<span class='wd_tests'>[https://tools.wmflabs.org/fiwiki-tools/testit/" .. tunnus ..  "/FAIL/NIMI Virheellinen NIMI-arvo]</span>";
		return ret;
	end
end

-- Funktio yrittää tarkistaa "342 – 43243" -tyyppiset arvovälit myös.
function validator._parse_number_str(target_str, min_value, max_value)
	local number_match_1 = "^(-?%d+[.,]?%d*)[– ]+-?(%d+[.,]?%d*)$";
	local number_match_2 = "^(-?%d+[.,]?%d*)%s*[+]%s*(-?%d+[.,]?%d*)$";
	local number_match_3 = "^(-?%d+[.,]?%d*)%s*[/]%s*(-?%d+[.,]?%d*)$";
	local number_match_4 = "^(-?%d+[.,]?%d*)$";
	local numbers={};
	local delim = " – ";

--  arvo: 1 – 10 
	local numbers ={ string.match( target_str,  number_match_1)};
	
-- arvo: 1 + 10
	if numbers[1] == nil then
		numbers = {string.match( target_str,  number_match_2)};
		delim=" + ";
	end	

-- arvo: 1 / 10
	if numbers[1] == nil then
		numbers = {string.match( target_str,  number_match_3)};
		delim=" / ";
	end	

-- arvo: 1
	if numbers[1] == nil then
		numbers = {string.match( target_str,  number_match_4)};
	end
	if numbers[1] == nil then
		return "unknown_value_error";
    end	
	for k,v in pairs(numbers) do
		local number=tonumber(tostring(mw.ustring.gsub(v, ",", ".")));
		if max_value ~= nil and max_value<number then
			return "max_value_error";
		elseif min_value ~= nil and min_value>number then
			return "min_value_error";
		end
		local lang= mw.getContentLanguage();
		
-- Don't formatNum too small numbers because exponential notation
		if number == 0 or number > 0.0001 or number < -0.0001 then
			numbers[k]=lang:formatNum(number);
		else
			numbers[k]=string.format("%f", number);
		end
	end
	
	local ret=numbers[1];
	if numbers[2] ~= nil then
		ret = ret .. delim .. numbers[2];
	end
	return ret;
end
-- Tarkistaa onko kuva-parametrin sisältö wikikoodia, jos ei ole
-- niin tarkistetaan sisältääkö parametri kuvan nimen
-- jos sisältää, niin tulostetaan nimen ympärille kuvan vaatima wikikoodi.

function validator.wikify_image_name(frame)
    local new_args = validator._getParameters( frame.args, {'image', 'image_width', 'image_description', 'wikidata_image', 'wikidata_image_description'} );
    local image=new_args['image'] or '';
    local image_width=new_args['image_width'] or '';
    local image_description=new_args['image_description'] or '';
    local wikidata_image=new_args['wikidata_image'] or 'P18';
    local wikidata_image_description=new_args['wikidata_image_description'] or 'P2096';
    
    local wikicode_tests={"%[%[", "%]%]", "%{%{", "%}%}", "%|"};
    local supported_file_formats={"jpg", "jpeg", "png", "gif", "svg", "tif", "tiff", "xcf"};
    
    if image=="" then
    	return ;
    end
    
	if image=="-" then
    	return ;
    end
    
    if image_width == '' then
    	image_width="250px";
    end
    
    -- Tarkistetaan onko syöte wikikoodia
    local s=string.lower(image);
	for i, test_pattern in ipairs(wikicode_tests) do
  		local result=mw.ustring.match( s,  test_pattern, 1 );
  		if result ~= nil then
  			return image;
  		end
	end
	-- Tarkistetaan onko syöte kuvan nimi
	for i, test_pattern in ipairs(supported_file_formats) do
  		local result=mw.ustring.match( s,  test_pattern .."$", 1 );
  		if result ~= nil then
  			if image_description ~= "" then
	  			image="[[file:" .. image .."|" .. image_width .."|" .. image_description .."]]"; 
	  		else
	  			image="[[file:" .. image .."|" .. image_width .."]]";
	  		end
	  		break;
  		end
	end
	return image;
end

function validator.plain_number(frame)
    local new_args = validator._getParameters( frame.args, {'s', 'unit', 'template'} );
    local s = new_args['s'] or '';
    local start =  1;
    local type =  'integer';
    local template_name =   new_args['template'] or 'unknown';
    local unit = new_args['unit'] or '';
    
  	local pattern="^([+-]?%d+[.,]?%d*)$";
    local number_type_str = "desimaaliluku";
    local template_link_message;
	local lang= mw.getContentLanguage();

	result = mw.ustring.match( s,  pattern, start );
	if result ~= nil then
		local result_num=tonumber(tostring(mw.ustring.gsub(result, ",", ".")));
		local unit_message="";
		if unit ~= "" then
			unit_message= '&nbsp;' .. mw.text.trim(unit);
		end
-- Dont formatNum small numbers because exponential notation		
		if result_num > 0.0001 then
			result_num_message=lang:formatNum(result_num);
		else
			result_num_message=string.format("%f", result_num);
		end
		
		return result_num_message .. unit_message ;
	else
		local template_link_message='[[malline:' ..template_name ..'|' ..template_name ..']]';
		local format_error_message = 'Mallineen ' .. template_link_message .. ' parametrin <code><nowiki>{{{1}}}</nowiki></code> arvon muoto <code><nowiki>' .. s .. '</nowiki></code> on virheellinen. Arvon pitäisi olla desimaaliluku. ';
		local example_message = 'Esimerkki käytöstä: <code><nowiki>{{' ..template_name ..'|10000|km}}</nowiki></code> tulostaa tekstin "' .. lang:formatNum(10000) .. '&nbsp;km".';
        return s .. validator._error( format_error_message .. ' ' .. example_message) ;
	end
end

-- funktio tarkistaa onko syötteen alkuosa numero ja onko se haluttua tyyppiä,
-- mikäli ei ole, niin se tulostaa virheen jossa se kertoo mistä virhe tulee.
-- Mikäli syöte on "" (tyhjä) palautetaan "";
-- Mikäli syöte on "-" palautetaan "-" (Wikidata-moduulin taikasana sille ettei haluta mitään tulostettavan)

function validator.number_with_unit( frame )
    local new_args = validator._getParameters( frame.args, {'s', 'type', 'allowed units', 'template', 'parameter', 'default unit', 'error message', 'min', 'max', 'word_delim'} );
    local s = new_args['s'] or '';
    local start =  1;
    local type = new_args['type'] or 'integer';
    local allowed_units = new_args['allowed units'] or '$';
    local default_unit = new_args['default unit'];
    local template_name=new_args['template'] or 'tuntematon';
    local parameter_name=new_args['parameter'] or 'tuntematon';
    local error_message=new_args['error message'] or '';
    local max_value;
    local min_value;
    local word_delim=new_args['word_delim'] or ' ';

    if  new_args['max'] ~=nil then
    	max_value=tonumber(new_args['max']);
	end

    if  new_args['min'] ~=nil then
    	min_value=tonumber(new_args['min']);
	end
	
    local number_type_str;

-- Siivotaan syötettä sen verran, että yhdistetään numerot poistamalla välilyönnit
   s=validator.glue_numbers(s)

-- Poistetaan luvun alussa olevat kuvailevat sanat   
   local prefixlist = { 'noin', 'yli', 'alle', 'maks.', 'keskim.', 'min.', 'enimmillään' }
   local prefix = ""
   for i, testprefix in ipairs(prefixlist) do
		prefix_pattern =  "^" .. testprefix;
		result = mw.ustring.gsub( s, prefix_pattern, "" );
		if (s ~= result ) then
			s=result
			prefix=testprefix
			break
		end
   end

	if s == '-' then
		return '-';
    elseif s == '' then
        return '';
    end
    
    if type == 'integer' then
    	pattern="^(-?[0123456789/–+ ]+)";
    	number_type_str = "kokonaisluku";
    elseif type == 'float' then
    	pattern="^(-?[0123456789/–+ ,.]+)";
    	number_type_str = "desimaaliluku";
    else
  		return validator._error( 'Unknown type' .. type );
    end

    if math.abs(start) < 1 or math.abs(start) > mw.ustring.len( s ) then
        return validator._error( 'Requested start is out of range' );
    end

    local result;
    local unit;
    local test_pattern;

    local iterator = mw.text.gsplit(allowed_units, ",\s*");
    
    for w in iterator do
    	if w == "%" then
    		w = "%%"	
		end
    	test_pattern=pattern .. ' *' .. mw.text.trim(w);
    	unit = w;
		result = mw.ustring.match( s,  test_pattern, start );
		if result ~= nil then
			break;
		end
    end

-- Failback 1 - Tarkistetaan onko viitteeseen loppuvaa arvoa
	if result == nil and default_unit ~= nil then
		unit = default_unit;
		test_pattern =  pattern .. " *.%'";
		result = mw.ustring.match( s,  test_pattern, start );
		if result ~= nil then
-- korjataan test_pattern sellaiseksi ettei se riko viitettä myöhemmin
				test_pattern =  pattern ;
		end
	end

-- Failback 2 - Tarkistetaan onko eof-loppuista arvoa
	if result == nil and default_unit ~= nil then
		unit = default_unit;
		test_pattern =  pattern .. "$";
		result = mw.ustring.match( s,  test_pattern, start );
	end

-- Failback 3 - Tarkistetaan onko eof-loppuista arvoa jos syötteestä poistetaan ensin non-breaking space
	if result == nil and default_unit ~= nil then
		unit = default_unit;
		test_pattern =  pattern .. "$";
		s=mw.ustring.gsub(s, "&nbsp;", "");
		result = mw.ustring.match( s,  test_pattern, start );
	end
	
	-- Failback 4 - Tarkistetaan onko " " loppuista arvoa
	if result == nil and default_unit ~= nil then
		unit = default_unit;
		test_pattern =  pattern .. " ";
		result = mw.ustring.match( s,  test_pattern, start );
	end

	parameter_format_message= validator._get_unit_format_message(allowed_units, number_type_str, min_value, max_value);
	skip_parameter_name=parameter_name .." ohita tarkistus";
	if word_delim=='_' then
		skip_parameter_name=parameter_name .."_ohita_tarkistus";
	end
	how_to_skip_message=" – Asettamalla parametrin <code>" .. skip_parameter_name .. "=1</code> voit ohittaa tarkistuksen.";

	local template_link_message='[[malline:' ..template_name ..'|' ..template_name ..']]';

-- add prefix to error results

	local error_num = prefix .. " " .. s
    if result == nil then
    	local format_error_message = 'Mallineen ' .. template_link_message .. ' parametrin <code>' .. parameter_name.. '</code> muoto <code><nowiki>' .. s .. '</nowiki></code> on virheellinen. ';
        return error_num .. validator._error( format_error_message .. parameter_format_message .. error_message .. how_to_skip_message) ;
    else
    	result=mw.text.trim(result);
		result_num = validator._parse_number_str(result, min_value, max_value);
		if result_num == "unknown_value_error" then
	    	local format_error_message = 'Mallineen ' .. template_link_message .. ' parametrin <code>' .. parameter_name.. '</code> muoto <code><nowiki>' .. s .. '</nowiki></code> on virheellinen. ';
    	    return error_num .. validator._error( format_error_message .. parameter_format_message .. error_message .. how_to_skip_message) ;
		elseif result_num == "max_value_error" then
			local max_value_error_message='Mallineen ' ..template_link_message .. ' parametrin <code>' .. parameter_name.. '</code> arvo <code><nowiki>' .. s .. '</nowiki></code> on suurempi kuin sallittu maksimiarvo. ';
	        return error_num .. validator._error( max_value_error_message .. parameter_format_message ..  error_message .. how_to_skip_message ) ;
		elseif result_num == "min_value_error" then
			local min_value_error_message='Mallineen ' .. template_link_message .. ' parametrin <code>' .. parameter_name.. '</code> arvo  <code><nowiki>' .. s .. '</nowiki></code> on pienempi kuin sallittu minimiarvo. ';
	        return error_num .. validator._error( min_value_error_message .. parameter_format_message ..  error_message .. how_to_skip_message) ;
		else
			local tail = mw.ustring.gsub( s, test_pattern, "" );
	        return prefix .. " " .. result_num .. '&nbsp;' .. mw.text.trim(unit) .. " " .. tail ;
		end
    end
end



--[[
Helper function that populates the argument list given that user may need to use a mix of
named and unnamed parameters.  This is relevant because named parameters are not
identical to unnamed parameters due to string trimming, and when dealing with strings
we sometimes want to either preserve or remove that whitespace depending on the application.
]]
function validator._getParameters( frame_args, arg_list )
    local new_args = {};
    local index = 1;
    local value;
    
    for i,arg in ipairs( arg_list ) do
        value = frame_args[arg]
        if value == nil then
            value = frame_args[index];
            index = index + 1;
        end
        new_args[arg] = value;
    end
    
    return new_args;
end        

--[[
Helper function to handle error messages.
]]
function validator._error( error_str )
    local frame = mw.getCurrentFrame();
    local error_category = frame.args.error_category or 'Virheellinen arvo tietolaatikossa';
    local ignore_errors = frame.args.ignore_errors or false;
    local no_category = frame.args.no_category or false;
    
    if validator._getBoolean(ignore_errors) then
        return '';
    end
    
    local error_str = frame:preprocess( '<ref group="virhe"><strong class="error">Tarkistusvirhe: ' .. error_str .. '</strong></ref>');

    if error_category ~= '' and not validator._getBoolean( no_category ) then
        error_str = '[[Category:' .. error_category .. ']]' .. error_str;
    end        
    
    return error_str;
end

--[[
Helper Function to interpret boolean strings
]]
function validator._getBoolean( boolean_str )
    local boolean_value;
    
    if type( boolean_str ) == 'string' then
        boolean_str = boolean_str:lower();
        if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0' 
                or boolean_str == '' then
            boolean_value = false;
        else
            boolean_value = true;
        end    
    elseif type( boolean_str ) == 'boolean' then
        boolean_value = boolean_str;
    else
        error( 'No boolean value found' );
    end    
    return boolean_value
end

--[[
Helper function that escapes all pattern characters so that they will be treated 
as plain text.
]]
function validator._escapePattern( pattern_str )
    return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" );
end

function validator._get_unit_format_message(allowed_units, number_type, min_value, max_value)
    local delim="";
    local allowed_units_message="";
    local plural_units=false;
    local iterator = mw.text.gsplit(allowed_units, ",\s*");
	for w in iterator do
		if delim ~= "" then
			plural_units = true;
		end
		allowed_units_message=allowed_units_message .. delim .. "<code>" ..  w .."</code>" ;
		delim=", ";
	end
	if plural_units then
		allowed_units_message = " yksikön jokin seuraavista: " ..  allowed_units_message ..". ";
	else
		allowed_units_message =  " yksikön " ..  allowed_units_message ..". ";
	end;

	local parameter_format_message="Arvon pitäisi olla  <code>" .. number_type .."</code> ja " .. allowed_units_message;
	
	if min_value ~=nil then
		parameter_format_message = parameter_format_message .. " Parametrin pienin sallittu arvo on <code>" .. min_value .."</code> ";
		if max_value ~=nil then
			parameter_format_message = parameter_format_message .. " ja suurin <code>" .. max_value .."</code>";
		end
		parameter_format_message = parameter_format_message .. ". ";
	elseif max_value ~=nil then
			parameter_format_message = parameter_format_message .. " Parametrin suurin sallittu arvo on <code>" .. max_value .."</code>. ";
	end
	
	return parameter_format_message;
end


return validator