Modul:Formattal
Dette Lua-modul bruges på ca. 8.500 sider, og derfor vil ændringer blive bemærket af mange. Vær venlig at afprøve enhver ændring i modulets /sandkasse eller /test-undersider, eller i din egen module sandkasse. Du bør også overveje at diskutere ændringer på diskussionssiden før du implementerer dem. |
Utilstrækkelig vejledning Dette modul bør have en (bedre) vejledning, helst med eksempler på anvendelse. Hvis andre moduler er nyere og/eller bedre, bør der henvises til dem. |
Brug
{{#invoke:Formattal|function_name}}
local p = {}
p.angivFejlSynligt = false
-- Angiver om fejl skal angives synligt med en rød stjerne efter det
-- fejlende tal, med en fejlbeskrivelse som viser sig, når man holder musen
-- over stjernen. Nogle mener, at det ikke vil være godt altid at angive
-- synlige fejl, da det måske vil kunne forvirre almindelige brugere.
-- Derfor kan funktionaliteten slås til og fra her, fx midlertidigt mens
-- man fejlsøger sider, der er havnet i kategorien "Sider med tal hvis
-- format ikke kendes af formattal".
-- angivFejlSynligt placeres i p for at unittests kan tjekke værdien.
local fejlkategorikode = "[[Kategori:Sider med tal hvis format ikke kendes af formattal]]"
-- Wikikode som altid sættes til sidst i output, hvis der er fejl i tallet.
-- Denne kode medtages altid ved fejl, uanset værdien af p.angivFejlSynligt.
--[[
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.angivFejlSynligt)
end
--[[
p.formaterIndledendeTalPaaDansk()
Funktion som indeholder hele kernefunktionaliteten på "ren" form (uden
frame.args[1] or ""), for at gøre unittests nemmere.
]]
function p.formaterIndledendeTalPaaDansk(originaltInputEllerTom,
angivFejlSynligt)
-- 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.opdelITalOgRest(inputUdenEventuelleHtmlKommentarerOgBlanktegn)
-- Fortolk tallet, hvis der er et, og dan det færdige output, inklusive
-- eventuelle fejlangivelser. Bemærk at der altid er et gyldigt output,
-- også hvis der er fejl.
local faerdigtOutput = p.fortolkTalOgDanFaerdigtOutput(inputtalEllerTom,
inputrest, angivFejlSynligt)
return faerdigtOutput
end
function p.fjernHtmlkommentarerOgYdreBlanktegn(s)
-- Fjern HTML-kommentarer.
local sUdenEventuelleHtmlKommentarer
= string.gsub(s, "<!%-%-.-%-%->", "")
-- Mon dette mønster virkelig er helt gennemtænkt?
-- Fjern blanktegn i enderne
local sUdenEventuelleHtmlKommentarerOgYdreBlanktegn
= mw.text.trim(sUdenEventuelleHtmlKommentarer)
return sUdenEventuelleHtmlKommentarerOgYdreBlanktegn
end
--[[
Opdel strengen s i hvad der kan minde om et tal (fx "-1.234.553,43") og
resten (fx "<ref>…</ref>" eller "").
Bemærk at det "der kan minde om et tal" ikke behøver faktisk være et
korrekt tal. Det skal blot matche et simplistisk mønster.
Eksempler:
"-1.234.553,43<ref>…</ref>" → "-1.234.553,43", "<ref>…</ref>"
"-<ref>…</ref>" → "-", "<ref>…</ref>"
".2blabla" → ".2", "blabla"
"..2..,,. blabla" → "..2..,,.", " blabla"
"+..,,..,,. blabla" → "+..,,..,,.", " blabla"
"blabla" → "", "blabla"
]]
function p.opdelITalOgRest(s)
-- Opdel i tal (fx "-1.234.553,43") og rest (fx "<ref>…</ref>" eller "").
-- Mønsteret er designet, så det ikke kan fejle; hverken talEllerTom
-- eller rest kan derfor blive nil.
local talEllerTom, rest = s:match("^([+-]? *[%d,.]*)(.*)$")
return talEllerTom, rest
-- Ingen af disse kan være nil, men begge kan være tomme.
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.
inputtalEllerTom (string, ikke nil)
Det tal, der skal fortolkes, eller den tomme streng. Den tomme streng
er en gyldig situation, som ikke giver fejl.
Kan være et ikke-fortolkbart "tal" a la "23.43.3,34", i hvilket
tilfælde der returneres en fejlkategori (det bør der i hvert fald).
Hvis angivFejlSynligt er true angives der også en synlig fejlangivelse
med fejlmeddelelse.
inputrest (string, ikke nil)
Den del af inputtet, der følger efter tallet. Hvis "tallet" er tomt,
er inputrest hele inputtet.
angivFejlSynligt (boolean, ikke nil)
Om fejl og fejlbeskrivelse skal angives synligt i output til højre
for det fejlende tal.
]]
function p.fortolkTalOgDanFaerdigtOutput(
inputtalEllerTom,
inputrest,
angivFejlSynligt)
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,
angivFejlSynligt)
end
end
return faerdigtOutput
end
--[[
p.opdelTal()
Returnerer fire værdier:
eventueltFortegnEllerTom,
heltalsdelUdenTusindadskillere,
decimalerEllerNil,
fejltekstEllerNil.
Hvis fejltekstEllerNil ikke er nil, gik opdelingen godt, og tallet er
accepteret som korrekt. Ellers indeholder fejltekstEllerNil en streng, som
beskriver fejlen.
Returværdien decimalerEllerNil 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 heltalsdelMedEventuelleTusindadskillereEllerNil,
decimaladskillerEllerNil, decimalerEllerNil
= inputtaldelUdenEventueltFortegn:match("^([%d,.]-)([,.])(%d%d?)$")
-- Afgør om der var et match.
-- Hvis ikke der er et match, vil både heltalsdelEllerNil,
-- decimaladskillerEllerNil og decimalerEllerNil være nil.
local derVarEtMatch = (heltalsdelMedEventuelleTusindadskillereEllerNil ~= nil)
if derVarEtMatch then
-- Der var et match: Tallet er et kommatal med 1 eller 2 decimaler.
-- Nye variable for at indikere, at de ikke længere kan være nil:
-- Værn mod bugs.
local heltalsdelMedEventuelleTusindadskillere
= heltalsdelMedEventuelleTusindadskillereEllerNil
local decimaladskiller = decimaladskillerEllerNil
-- Bestemt tusindadskillertegnet.
-- Bemærk at decimaladskiller logisk kun kan være "." eller ","
-- pga. mønsteret ovenfor.
local tusindadskiller = p.modsatAdskillertegn(decimaladskiller)
-- Fjern alle tusindadskillertegn fra heltalsdel.
heltalsdelUdenTusindadskillere, fejltekstEllerNil
= p.fjernTusindadskillere(heltalsdelMedEventuelleTusindadskillere, tusindadskiller)
else
-- Der var ikke et match. Hvis tallet er på korrekt form, er det et heltal.
local heltalsdelMedEventuellePunktumtusindadskillere
= inputtaldelUdenEventueltFortegn:match("^([%d.]+)$")
if heltalsdelMedEventuellePunktumtusindadskillere then
-- Fjern alle tusindadskillertegn fra heltalsdel.
heltalsdelUdenTusindadskillere, fejltekstEllerNil
= p.fjernTusindadskillere(heltalsdelMedEventuellePunktumtusindadskillere, ".")
else
local heltalsdelMedEventuelleKommatusindadskillere
= inputtaldelUdenEventueltFortegn:match("^([%d,]+)$")
if heltalsdelMedEventuelleKommatusindadskillere then
-- Fjern alle tusindadskillertegn fra heltalsdel.
heltalsdelUdenTusindadskillere, fejltekstEllerNil
= p.fjernTusindadskillere(heltalsdelMedEventuelleKommatusindadskillere, ",")
else
-- Angiv fejl.
heltalsdelUdenTusindadskillere = nil
fejltekstEllerNil = "Kunne ikke fortolke tallet '" .. inputtal .. "'."
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
decimalerEllerNil = nil
end
end
return eventueltFortegnEllerTom, heltalsdelUdenTusindadskillere, decimalerEllerNil, fejltekstEllerNil
end
--[[
Returner heltalsdelMedEventuelleTusindadskillere med alle tusindadskillere
af den angivne type ("." eller ",") fjernet.
heltalsdelMedEventuelleTusindadskillere:
En streng med nul eller flere cifre og tusindadskillere.
Returværdier:
heltalsdelUdenTusindadskillere (string),
fejltekstEllerNil (string eller nil)
Eksempler på korrekt opførsel, som dog endnu ikke er opnået:
"", "," → "", nil
"0", "," → "0", nil
"12", "," → "12", nil
"123", "," → "123", nil
"1234", "," → "1234", nil
"1,234", "," → "1,234", nil
"1,234", "." → nil, "Ugyldige tusindadskillere i heltalsdel"
"1,234.", "," → nil, "Ugyldige tusindadskillere i heltalsdel"
"1,234,567", "," → "1234567", nil
"1,234,56", "," → nil, "Ugyldig placering af tusindadskillere i heltalsdel"
]]
function p.fjernTusindadskillere(heltalsdelMedEventuelleTusindadskillere, tusindadskiller)
local heltalsdelUdenTusindadskillere
local fejltekstEllerNil
local talletErGyldigt = erGyldigtHeltalMedMuligeTusindadskillere(
heltalsdelMedEventuelleTusindadskillere, tusindadskiller)
if talletErGyldigt then
heltalsdelUdenTusindadskillere
= p.removeAll(
heltalsdelMedEventuelleTusindadskillere,
p.tusindadskillerpattern(tusindadskiller))
fejltekstEllerNil = nil
else
heltalsdelUdenTusindadskillere = nil
fejltekstEllerNil = "Ugyldigt placerede eller ugyldige tusindadskillere i heltalsdelen '" .. heltalsdelMedEventuelleTusindadskillere .. "'."
end
return heltalsdelUdenTusindadskillere, fejltekstEllerNil
end
function erGyldigtHeltalMedMuligeTusindadskillere(
heltalsdelMedEventuelleTusindadskillere, tusindadskiller)
local s = heltalsdelMedEventuelleTusindadskillere
-- Fjern grupper af "tddd" i slutningen indtil intet match, hvor t er
-- en tusindadskiller og d er et ciffer.
local pattern = p.tusindadskillerpattern(tusindadskiller) .. "%d%d%d$"
local numberOfReplacements
local totalNumberOfReplacements = 0
repeat
s, numberOfReplacements = s:gsub(pattern, "")
totalNumberOfReplacements = totalNumberOfReplacements + numberOfReplacements
until numberOfReplacements == 0
local theNumberIsValid
if totalNumberOfReplacements == 0 then
-- Der var ingen tusindadskillere, i hvert fald ikke korrekt placeret.
theNumberIsValid = p.isSequenceOfAtLeastOneDigit(s)
else
-- Der var tusindadskillere. Hvis resten er mellem 1 og 3 cifre, var
-- tallet korrekt.
theNumberIsValid = p.isSequenceOf1To3Digits(s)
end
return theNumberIsValid
end
function p.tusindadskillerpattern(tusindadskiller)
return "[" .. tusindadskiller .. "]"
end
function p.isSequenceOfAtLeastOneDigit(s)
return not not s:match("^%d+$")
end
function p.isSequenceOf1To3Digits(s)
return not not s:match("^%d%d?%d?$")
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, angivFejlSynligt)
if angivFejlSynligt 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,
angivFejlSynligt)
return
inputtal
.. p.fejlangivelseskode(fejltekst, angivFejlSynligt)
.. inputrest
.. fejlkategorikode
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