Spring til indhold

Modul:Formattal

Page extended-protected
Fra Wikipedia, den frie encyklopædi
Version fra 26. nov. 2017, 17:26 af Jhertel (diskussion | bidrag) Jhertel (diskussion | bidrag) (Accept af mellemrumstegn mellem fortegn og resten af tallet, men fjernelse af dem i output. Desuden opdatering af dokumentation for funktionen formattal (den haltede efter de nylige ændringer).)

local p = {}
p.angivFejlMedRoedStjerneOgFejlbeskrivelse = false
	-- Angiver om fejl skal angives med en rød stjerne efter tallet, med
	-- en fejlbeskrivelse som viser sig, når man holder musen over stjernen.
	-- Lægges i p for at unittests kan tjekke værdien.
local fejlkategorikode = "[[Kategori:Sider med tal hvis format ikke kendes af formattal]]"


--[[
  formattal(frame)
  
  Formaterer et tal med højst to decimaler på dansk. Det oprindelige tal kan 
  enten bruge punktum som decimaladskiller og komma som tusindadskiller 
  ("123,456,789.12"), eller komma som decimaladskiller og punktum som
  tusindadskiller ("123.456.789,12").
  
  Uanset hvordan tallet var formateret i input, formateres det i output på dansk
  vis med punktum som tusindadskiller og komma som decimaladskiller.

  Fortegn ("-" eller "+") håndteres også, og der kan endda være et vilkårligt
  antal mellemrum mellem fortegnet og resten af tallet, selv om dette er en 
  ukorrekt skrivemåde; dette rettes i output ved at disse mellemrum fjernes.
  
  Eksempler (se Modul:Formattal/testcases for langt flere eksempler):
    Opfattes som 6-cifret heltal, da tallet ikke må have mere end to decimaler:
      formattal("123456") = "123.456"
      formattal("123,456") = "123.456" 
      formattal("123.456") = "123.456"
    Opfattes som 5-cifret tal med 2 decimaler:
      formattal("123,45) = "123,45"
      formattal("123.45") = "123,45"
    Giver fejl for ukendt format (tusindadskiller og decimaltegn er ens):
      formattal("123.456.7") = "123.456.7" 
    Giver fejl for ukendt format (bruger forskellige tusindadskillere eller
    har for mange decimaler):
      formattal("123.456,789") = "123.456,789"

  Alle HTML-kommentarer og visse blanktegn i tallet ignores; andre uvedkommende
  tegn vil give fejl.

  Ved fejl returneres tallet uændret samtidig med at artiklen placeres i 
  fejlkategorien "Sider med tal hvis format ikke kendes af formattal".
]]
function p.formattal (frame)
    local originaltInputEllerTom = (frame.args[1] or "")
	
	return p.formaterIndledendeTalPaaDansk(
		originaltInputEllerTom, 
		p.angivFejlMedRoedStjerneOgFejlbeskrivelse)
end


--[[
	p.formaterIndledendeTalPaaDansk()
	
	Funktion som indeholder hele funktionaliteten på "ren" form (uden 
	frame.args[1]), for at gøre unittests nemmere.
]]
function p.formaterIndledendeTalPaaDansk(originaltInputEllerTom, 
		angivFejlMedRoedStjerneOgFejlbeskrivelse)
	
	-- Fjern HTML-kommentarer og ydre blanktegn.
    local inputUdenEventuelleHtmlKommentarerOgBlanktegn 
		= p.fjernHtmlkommentarerOgYdreBlanktegn(originaltInputEllerTom)

	-- Opdel i inputtal (fx "-1.234.553,43" eller "") 
	-- og inputrest (fx "<ref>…</ref>" eller "").
	local inputtalEllerTom, inputrest 
		= p.opdelInputITalOgRest(inputUdenEventuelleHtmlKommentarerOgBlanktegn)

	-- Fortolk tallet, hvis der er et, og dan det færdige output, inkl. evt. 
	-- fejlhåndtering.
	local faerdigtOutput = p.fortolkTalOgDanFaerdigtOutput(inputtalEllerTom, 
		inputrest, angivFejlMedRoedStjerneOgFejlbeskrivelse)
	
	return faerdigtOutput
	
end


--[[
	p.fortolkTalOgDanFaerdigtOutput()
	
	Fortolk tallet, hvis der er et, og dan det færdige output, inkl. evt. 
	fejlhåndtering. Hvis der ikke er et tal, så returner inputrest uændret.
]]
function p.fortolkTalOgDanFaerdigtOutput(inputtalEllerTom, inputrest, 
		angivFejlMedRoedStjerneOgFejlbeskrivelse)
	
	local faerdigtOutput 

	if inputtalEllerTom == "" then
		-- Taldelen er tom.
		-- Dette er en gyldig situation, hvor output så skal være
		-- identisk med input. Det antages dog, at det er okay at 
		-- HTML-kommentarer og ydre blanktegn stadig fjernes.
		-- Jf. https://da.wikipedia.org/wiki/Brugerdiskussion:Jhertel#Modul_formattal_og_tomt_input
		faerdigtOutput = inputrest  
			-- inputrest udgør det hele, da inputtalEllerTom er tom.

	else
		local inputtal = inputtalEllerTom  -- Den kan ikke længere være tom.
		
		-- Opdel tallet (inputtal) i enkeltdele.
		-- Dette kan fejle, og i så fald vil fejltekstEllerNil være ikke-nil og 
		-- indeholde en fejlbeskrivelse.
	    local eventueltFortegnEllerTom, heltalsdelUdenTusindadskillere, 
	    	decimalerEllerNil, fejltekstEllerNil = p.opdelTal(inputtal)
	
		-- Returner output (enten et formateret tal eller fejl).
		if not fejltekstEllerNil then
	    	-- Succes. Tallet er fuldt fortolket og opdelt i enkeltdele.
	    	
	    	-- Dan et færdigt dansk formateret tal.
	    	local outputtal
	    		= p.danDanskOutputtal(
	    			eventueltFortegnEllerTom, 
					heltalsdelUdenTusindadskillere, 
					decimalerEllerNil) 
	
			faerdigtOutput = outputtal .. inputrest
	
		else
	    	-- Fejlsituation.
	    	
    		-- FejltekstEllerNil kan ikke længere være nil. Angiv dette klart 
    		-- som et værn mod bugs.
	    	local fejltekst = fejltekstEllerNil 

	        faerdigtOutput = p.danSamletFejlreturstreng(
	        	inputtal, 
	        	inputrest, 
	        	fejltekst, 
	        	angivFejlMedRoedStjerneOgFejlbeskrivelse)

	    end
	end

	return faerdigtOutput
	
end


function p.opdelInputITalOgRest(s)

	-- Opdel i inputtal (fx "-1.234.553,43") og inputrest (fx "<ref>…</ref>" eller "").
	-- Mønsteret er designet, så det ikke kan fejle; hverken inputtalEllerTom 
	-- eller inputrest kan derfor blive nil.
	local inputtalEllerTom, inputrest = s:match("^([+-]? *[%d,.]*)(.*)$")
	
	return inputtalEllerTom, inputrest -- Ingen af disse kan være nil.
	
end


function p.fjernHtmlkommentarerOgYdreBlanktegn(s)
	
	-- Fjern HTML-kommentarer.
    local sUdenEventuelleHtmlKommentarer 
    	= string.gsub(s, "<!%-%-.-%-%->", "") 

    -- Fjern blanktegn i enderne
    local sUdenEventuelleHtmlKommentarerOgYdreBlanktegn
    	= mw.text.trim(sUdenEventuelleHtmlKommentarer)
    	
    return sUdenEventuelleHtmlKommentarerOgYdreBlanktegn
end


function p.danDanskOutputtal(eventueltFortegnEllerTom, 
		heltalsdelUdenTusindadskillere, decimalerEllerNil)

	local heltalsdelMedDanskeTusindadskillere 
		= p.indsaetTusindadskillere(heltalsdelUdenTusindadskillere)
			-- Giver fx "123.456.789" eller "123".

	local danskDecimaldel
		= p.danDanskDecimaldel(decimalerEllerNil) -- Giver fx ",12", ",6" eller "".

	local samletTal
		= eventueltFortegnEllerTom 
			.. heltalsdelMedDanskeTusindadskillere 
			.. danskDecimaldel

    return samletTal
end


function p.danDanskDecimaldel(decimalerEllerNil)

    if decimalerEllerNil then
		return "," .. decimalerEllerNil
	else
		return ""
	end
	
end


function p.fejlangivelseskode(fejltekst, angivFejlMedRoedStjerneOgFejlbeskrivelse)
	
	if angivFejlMedRoedStjerneOgFejlbeskrivelse then
		return '<sup><span style="color:red" title="Skabelonen Formattal kan ikke konvertere dette tal: ' .. fejltekst .. '">*</span></sup>'
	else
		return ""
	end
	
end


function p.danSamletFejlreturstreng(inputtal, inputrest, fejltekst, 
	angivFejlMedRoedStjerneOgFejlbeskrivelse)
     
    return 
    	inputtal 
    	.. p.fejlangivelseskode(fejltekst, angivFejlMedRoedStjerneOgFejlbeskrivelse)
    	.. inputrest 
    	.. fejlkategorikode

end


--[[
	p.opdelTal()

	Returnerer 
		eventueltFortegnEllerTom, heltalsdelUdenTusindadskillere, decimaler, fejltekstEllerNil.

	Hvis fejltekstEllerNil ikke er nil, gik det godt. Ellers indeholder fejltekstEllerNil en 
	beskrivelse af fejlen.

	Decimaler er nil, hvis der ingen decimaler er.
]]
function p.opdelTal(inputtal)

	local fejltekstEllerNil = nil
	local heltalsdelUdenTusindadskillere = nil

    -- Fjern et evt. fortegn på første position og husk det.
    local eventueltFortegnEllerTom, inputtaldelUdenEventueltFortegn
    	= p.opsplitIFortegnOgRest(inputtal)

	-- Prøv at matche et kommatal med 1 eller 2 decimaler.
    local heltalsdelMedEventuelleTusindadskillere, decimaladskiller, decimaler
    	= inputtaldelUdenEventueltFortegn:match("^([%d,.]-)([,.])(%d%d?)$")

	-- Afgør om der var et match.
	-- Hvis ikke der er et match, vil både heltalsdel, decimaladskiller 
	-- og decimaler være nil.
	local derVarEtMatch = (heltalsdelMedEventuelleTusindadskillere ~= nil)

    if derVarEtMatch then
    	-- Der var et match: Tallet er et kommatal med 1 eller 2 decimaler.
    	
    	-- Bestemt tusindadskillertegnet.
    	-- Bemærk at decimaladskiller logisk kun kan være "." eller ","
    	-- pga. mønsteret ovenfor.
    	local tusindadskillertegnIInput = p.modsatAdskillertegn(decimaladskiller)

    	-- Fjern alle tusindadskillertegn fra heltalsdel.
        heltalsdelUdenTusindadskillere 
        	= p.removeAll(heltalsdelMedEventuelleTusindadskillere, "[" .. tusindadskillertegnIInput .. "]")

    else
    	-- Der var ikke et match. Hvis tallet er på korrekt form, er det et heltal.

	    local heltalsdelMedEventuellePunktumtusindadskillere
    		= inputtaldelUdenEventueltFortegn:match("^([%d.]+)$")
    	
    	if heltalsdelMedEventuellePunktumtusindadskillere then
    		heltalsdelUdenTusindadskillere 
    			= p.removeAll(heltalsdelMedEventuellePunktumtusindadskillere, "%.")
    	else
		    local heltalsdelMedEventuelleKommatusindadskillere
	    		= inputtaldelUdenEventueltFortegn:match("^([%d,]+)$")

			if heltalsdelMedEventuelleKommatusindadskillere then
	    		heltalsdelUdenTusindadskillere 
	    			= p.removeAll(heltalsdelMedEventuelleKommatusindadskillere, ",")
			else
				-- Angiv fejl.
				heltalsdelUdenTusindadskillere = nil  
				
				if inputtal == "" then
					fejltekstEllerNil = "Tallet er tomt."
				else
					fejltekstEllerNil = "Kunne ikke fortolke tallet '" .. inputtal .. "'."
				end
			end
		end
    end

	if not fejltekstEllerNil then
		-- Håndter situationer som ".12" = 0,12.
		if heltalsdelUdenTusindadskillere == "" then
			heltalsdelUdenTusindadskillere = "0"
		end
	
		-- Tjek for ugyldige tusindadskillere.
	    if heltalsdelUdenTusindadskillere:match("%D") then
	    	-- Fejlsituation: Heltalsdelen uden tusindadskillere indeholder
	    	-- mindst ét ikke-ciffer (%D), som kun kan være en forkert
	    	-- tusindadskiller.
	    	fejltekstEllerNil = "Heltalsdelen '" .. heltalsdelUdenTusindadskillere .. "' indeholder ugyldige tusindadskillere; hele tallet er '" .. inputtal .. "'."
	    	eventueltFortegnEllerTom = ""
	    	heltalsdelUdenTusindadskillere = nil
	    	decimaler = nil
		end
	end
	
	return eventueltFortegnEllerTom, heltalsdelUdenTusindadskillere, decimaler, fejltekstEllerNil
end


function p.removeAll(s, pattern)
	return s:gsub(pattern, "")
end


--[[
	p.modsatAdskillertegn(adskillertegn)
	
	Returner det modsatte adskillertegn til det givne.
	
	adskillertegn:
		Skal være enten "." eller ",".

	Returværdi:
		"," hvis adskillertegn er ".".
		"." hvis adskillertegn er "," (eller hvad som helst andet).
]]
function p.modsatAdskillertegn(adskillertegn)
    if adskillertegn == "." then
    	return ","
    else
        return "."
    end
end

function p.opsplitIFortegnOgRest(s)

    local eventueltFortegnEllerTom, reststrengUdenFortegn = s:match("^([+-]?) *(.*)$")

	return eventueltFortegnEllerTom, reststrengUdenFortegn

end

--[[
	p.indsaetTusindadskillere(heltalsStreng)
	
	Hjælpefunktion. 
	
	Indsæt tusindadskillere i en given streng af cifre.

	Eksempler:	
		p.indsaetTusindadskillere("") == ""
		p.indsaetTusindadskillere("1") == "1"
		p.indsaetTusindadskillere("12") == "12"
		p.indsaetTusindadskillere("123") == "123"
		p.indsaetTusindadskillere("1234") == "1.234"
		p.indsaetTusindadskillere("123456789") == "123.456.789"
]]
function p.indsaetTusindadskillere(heltalsstreng)
	local akkumulator = heltalsstreng -- Heltal uden tusindadskillere, fx "123456789".
	local antalTusindAdskillereIndsat

    repeat  
        akkumulator, antalTusindAdskillereIndsat 
        	= string.gsub(akkumulator, "^(%d+)(%d%d%d)", '%1.%2')
    until antalTusindAdskillereIndsat == 0

    return akkumulator  -- Heltal med tusindadskillere, fx "123.456.789".
end

return p