Actions

Module

« Unité » : différence entre les versions

De Wikimanche

wm>Zebulon84
(_unite : vrai signe moins pour les exposants, et affiche les exposants non numériques)
(Mise à jour)
 
(34 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
local p = {}
local p = {}
-- local Delink = require( 'Module:Delink' ) -- chargé uniquement si nécessaire


-- Chargement de la base de données des nom d'unités avec gestion d'erreur.
-- Chargement de la base de données des nom d'unités avec gestion d'erreur.
Ligne 5 : Ligne 7 :
local dataSuccess, Data = pcall ( mw.loadData, moduleData )
local dataSuccess, Data = pcall ( mw.loadData, moduleData )
if dataSuccess and type( Data ) == 'table' then
if dataSuccess and type( Data ) == 'table' then
dataSuccess = type( Data.unit ) == 'table'  
dataSuccess = type( Data.unit ) == 'table'
and type( Data.prefix ) == 'table'
and type( Data.prefix ) == 'table'
and type( Data.exposant ) == 'table'
and type( Data.exposant ) == 'table'
end
end
local errorCat = '[[Catégorie:Page incorrectement traitée par le Module:Unité]]'
local addErrorCat = false
local supUnicode = { ['0'] = '⁰', ['1'] = '¹', ['2'] = '²', ['3'] = '³', ['4'] = '⁴', ['5'] = '⁵', ['6'] = '⁶', ['7'] = '⁷', ['8'] = '⁸', ['9'] = '⁹',
['+'] = '⁺', ['-'] = '⁻', ['='] = '⁼', ['('] = '⁽', [')'] = '⁾', ['n'] = 'ⁿ' }
local subUnicode = { ['0'] = '₀', ['1'] = '₁', ['2'] = '₂', ['3'] = '₃', ['4'] = '₄', ['5'] = '₅', ['6'] = '₆', ['7'] = '₇', ['8'] = '₈', ['9'] = '₉',
['a'] = 'ₐ', ['e'] = 'ₑ', ['o'] = 'ₒ', ['x'] = 'ₓ', ['h'] = 'ₕ', ['k'] = 'ₖ', ['l'] = 'ₗ',
['m'] = 'ₘ', ['n'] = 'ₙ', ['p'] = 'ₚ', ['s'] = 'ₛ', ['t'] = 'ₜ',
}
local fractionUnicode = { ['½'] = '1/2', ['⅓'] = '1/3', ['⅕'] = '1/5', ['⅙'] = '1/6', ['⅛'] = '1/8',
['⅔'] = '2/3', ['⅖'] = '2/5', ['⅚'] = '5/6', ['⅜'] = '3/8', ['¾'] = '3/4', ['⅗'] = '3/5',
['⅝'] = '5/8', ['⅞'] = '7/8', ['⅘'] = '4/5', ['¼'] = '1/4', ['⅐'] = '1/7', ['⅑'] = '1/9', ['⅒'] = '1/10', ['↉'] = '0/3',
}
local nbsp = '\194\160'        -- espace insécable
local nnbsp = '\226\128\175'  -- espace fine insécable


--- Copie de Outils.trim acceptant les nombres.
--- Copie de Outils.trim acceptant les nombres.
local function trim( texte )
local function trim( texte )
if type( texte ) == 'string' then
if type( texte ) == 'string' then
texte = texte:gsub( '^%s*(.*)%f[%s]%s*$', '%1' )
-- http://lua-users.org/wiki/StringTrim
texte = texte:match( '^()%s*$' ) and '' or texte:match( '^%s*(.*%S)' )
if texte ~= '' then
if texte ~= '' then
return texte
return texte
Ligne 22 : Ligne 41 :
end
end


-- retire les chiffres des strip markers
local function escapeStripMarkers( input )
return input:gsub( '(UNIQ%-%-%a+%-)(%x%x%x%x%x%x%x%x)(%-QINU)', function ( leading, hexdigits, trailing )
local escapeddigits = hexdigits:gsub( '%d', {
['0'] = 'g', ['1'] = 'h', ['2'] = 'i', ['3'] = 'j', ['4'] = 'k',
['5'] = 'l', ['6'] = 'm', ['7'] = 'n', ['8'] = 'o', ['9'] = 'p',
} )
return leading .. escapeddigits .. trailing
end )
end
-- restaure les strip markers
local function restoreStripMarkers( input )
return input:gsub( '(UNIQ%-%-%a+%-)(%a%a%a%a%a%a%a%a)(%-QINU)', function ( leading, escapeddigits, trailing )
local hexdigits = escapeddigits:gsub( '%a', {
['g'] = '0', ['h'] = '1', ['i'] = '2', ['j'] = '3', ['k'] = '4',
['l'] = '5', ['m'] = '6', ['n'] = '7', ['o'] = '8', ['p'] = '9',
} )
return leading .. hexdigits .. trailing
end )
end
-- remplacement de certains caractères, pour simplifier les pattern
function p.sanitizeNum( nombre )
function p.sanitizeNum( nombre )
if type( nombre ) == 'number' then
if type( nombre ) == 'number' then
return tostring( nombre )
return tostring( nombre )
elseif type( nombre ) == 'string' then
elseif type( nombre ) == 'string' then
if nombre:match( '^%-?[%d.,]+$' ) then
return nombre
end
local result = nombre
local result = nombre
-- trim
-- remplacement des signes moins par un tiret
:gsub( '^%s*(.*)%f[%s]%s*$', '%1' )
-- remplacement des signes moins ou demi-cadratin par un tiret
:gsub( '%−%f[%d]', '-')  -- U+2212
:gsub( '%−%f[%d]', '-')  -- U+2212
:gsub( '−%f[%d]', '-')  -- html −
:gsub( '−%f[%d]', '-')  -- html −
:gsub( '\226\128[\146\147]%f[%d]', '-') -- U+2212, U+2213 (tiret numérique et demi-cadratin)
-- remplacement des espaces insécable par des espace simple
-- remplacement des espaces insécable par des espace simple
:gsub( '\194\160', ' ' )
:gsub( nbsp, ' ' )
:gsub( ' ', ' ' )
:gsub( ' ', ' ' )
:gsub( nnbsp, ' ' )
:gsub( ' ', ' ' )
:gsub( ' ', ' ' )
:gsub( '\226\128[\132-\138]', ' ' ) -- U+2004 à U+200A
:gsub( ' ', ' ' )
-- trim
:gsub( '^%s*(%S?.-)%s*$', '%1' )
return result
return result
else  
else
return ''
return ''
end
end
Ligne 55 : Ligne 106 :
if result == '' then
if result == '' then
return ''
return ''
elseif not result:match( '^%-?[%d., ]*%d$' ) then
end
-- si nombre est un chiffre en exposant ou indice comme ², retourne ce chiffre
for i = 0, 9 do
local is = tostring(i)
if result == supUnicode[ is ] or result == subUnicode[ is ] then
return is
end
end
if not result:match( '^%-?[%d., ]*%d$' ) and not result:match( '^%-?[%d., ]*%d ?e[+-]?%d+$' ) then
return nombre
return nombre
end
end
end
end
 
-- suppression espaces
-- suppression espaces
result = result:gsub( ' ', '' )
result = result:gsub( ' ', '' )
 
-- gestion des points et des virgules
if result:match( '[.,]' ) then
if result:match( '[.,]' ) then
if result:match( '%d+,%d%d%d,%d%d%d%f[%D]' ) -- type 1,234,567  
if result:match( '%d%.%d%d%d%.%d' ) then
or result:match( '%d+,%d%d%d%.%d+' )  -- type 1,234.5
-- type 12.345.678
--or nombre:match( '%d+,%d00$' ) -- type 1,200
result = result:gsub( '%.', '' ):gsub( ',', '.' )
elseif result:match( '%d,%d%d%d,%d' ) -- type 1,234,567 ou 1.234,567,8
or result:match( '%d,%d%d%d%.%d' )  -- format anglo-saxon type 1,234.5
or result:match( '%d%.%d%d%d,%d' ) -- type 1.123,56 (utilisé en exemple pour sépararer les décimales avec l'ancien modèle unité ou formatnum)
then
then
-- format anglo-saxon
result = result:gsub( ',', '' )
result = result:gsub( ',', '' )
elseif result:match( '%d+%.%d%d%d,%d' ) or result:match( '%d+%.%d%d%d%.%d%d%d%f[%D]' ) then
-- formant germanique type 1.234,5
result = result:gsub( '%.', '' ):gsub( ',', '.' )
else
else
result = result:gsub( ',', '.' )
result = result:gsub( ',', '.' )
Ligne 84 : Ligne 143 :
-- _formantNum transforme un nombre ou une chaine représentant un nombre en chaine formatée suivant les conventions du français
-- _formantNum transforme un nombre ou une chaine représentant un nombre en chaine formatée suivant les conventions du français
-- si le paramètre ne représente pas un nombre lua il est retourné sans modification
-- si le paramètre ne représente pas un nombre lua il est retourné sans modification
function p._formatNum( num )
-- Le paramètre peut être transmis sous forme de table pour ajouter des options :
-- * round : arrondi à n chiffre après la virgule (peut être négatif)
-- * decimals : nombre de décimales affichées (peut être négatif, dans ce cas équivalent à round)
-- * noHtml : n'utilise pas de balise HTML pour affiché les puissance de 10 (pour pouvoir être utilisé en title)
function p.formatNum( num )
local params = {}
if type( num ) == 'table' then
params = num
num = params[1]
end
if type( num ) == 'number' then
if type( num ) == 'number' then
num = tostring( num )
num = tostring( num )
Ligne 90 : Ligne 158 :
return num
return num
end
end
 
local moins, entier, fraction = num:match( '^(%-?)(%d*)%.?(%d*)$' )
-- séparation exposant
local n, exponent = num:match( '^([-%d.]+)[eE]([+-]?%d+)$' )
if exponent then
num = n
if params.noHtml then
exponent = exponent:gsub('+?%f[%d]0', '' )
:gsub( '[%d-]', supUnicode )
else
exponent = '<sup>' .. exponent:gsub('^%+?(%-?)0?', { ['-'] = '−', [''] = '' } ) .. '</sup>'
end
if num == '1' then
return '10' .. exponent
end
exponent = nbsp .. '×' .. nnbsp .. '10' .. exponent
else
exponent = ''
end
 
-- arrondi
local decimals = tonumber( params.decimals )
local round = tonumber( params.round ) or decimals
if round and tonumber( num ) then
local mult = 10 ^ round
num = tostring( math.floor( num * mult + 0.5 ) / mult )
end
 
local moins, entier, deci = num:match( '^(%-?)(%d*)%.?(%d*)$' )
if not entier then
if not entier then
return num
return num
end
end
 
if moins == '-' then
if moins == '-' then
moins = '−' -- signe moins (U+2212)
moins = '−' -- signe moins (U+2212)
end
end
 
if entier == '' then
if entier == '' then
entier = '0'
entier = '0'
elseif entier:len() > 3 then
elseif entier:len() > 3 then
local ini = math.fmod( entier:len() - 1, 3 ) + 1
local ini = math.fmod( entier:len() - 1, 3 ) + 1
entier = ( entier:sub( 1, ini ) or '') .. entier:sub( ini + 1 ):gsub( '(%d%d%d)', '\194\160%1' )
entier = ( entier:sub( 1, ini ) or '') .. entier:sub( ini + 1 ):gsub( '(%d%d%d)', nbsp .. '%1' )
end
end
if fraction ~= '' then
if deci ~= '' or ( decimals and decimals > 0 ) then
fraction = ',' .. fraction:gsub( '(%d%d%d)', '%1\194\160' ):gsub( '\194\160$', '' )
if decimals and decimals > #deci then
deci = deci .. string.rep( '0', decimals - #deci )
end
if #deci > 3 then
deci = ',' .. deci:gsub( '(%d%d%d)', '%1' .. nbsp ):gsub( nbsp .. '$', '' )
else
deci = ',' .. deci
end
end
end
return moins .. entier .. fraction
end


---
return moins .. entier .. deci .. exponent
-- formatNum transforme les nombres d'une chaine en chaine formatée suivant les conventions du français.
-- Le nombre fourni doit un de type number ou chaine équivalente :
-- pas de séparateur de millier, point comme séparamèteur décimal, tiret comme signe moins.
-- Équivalent de FormatNum, mais avec vrai signe moins et séparateur de millier en partie décimale.
function p.formatNum( num )
if type( num ) == 'number' then
return p._formatNum( num )
elseif type( num ) == 'string' then
return num:gsub( '%-?%d*%.?%d+', p.formatNombre )
else
return ''
end
end
end


Ligne 131 : Ligne 217 :
-- formatNombre transforme un nombre formaté ou non en chaine formatée suivant les convention du français.
-- formatNombre transforme un nombre formaté ou non en chaine formatée suivant les convention du français.
-- si la chaine n'est pas reconnu comme un nombre, elle n'est pas modifiée.
-- si la chaine n'est pas reconnu comme un nombre, elle n'est pas modifiée.
function p.formatNombre( nombre )
function p.formatNombre( num, round, decimals )
return p._formatNum( p.parseNombre( nombre ) )
return p.formatNum{ p.parseNombre( num ), round = round, decimals = decimals }
end
end


--- formatNombres transforme tous les nombres d'une chaine en nombre formaté suivant les conventions du français.
--- formatNombres transforme tous les nombres d'une chaine en nombre formaté suivant les conventions du français.
function p.formatNombres( texte )
function p.formatNombres( nombres, round, decimals )
if type( texte ) == 'number' then
if type( nombres ) == 'number' then
return p.formatNum( texte )
return p.formatNum{ nombres, round = round, decimals = decimals }
elseif type( texte ) == 'string' then
elseif type( nombres ) == 'string' then
return texte:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre )
-- retire les chiffres des strip markers
else  
nombres = escapeStripMarkers( nombres )
 
-- formatage proprement dit
nombres = p.sanitizeNum( nombres )
local formatN = function ( n )
return p.formatNombre( n, round, decimals )
end
if nombres:match('%d%-%d') then
nombres = nombres:gsub( '%f[%d.,][%d., ]*%d', formatN )
else
nombres = nombres
:gsub( '%-?%f[%d.,][%d., ]*%d ?e[+-]?%d+', formatN )
:gsub( '%-?%f[%d.,][%d., ]*%d', formatN )
end
 
-- restaure les strip markers
nombres = restoreStripMarkers( nombres )
 
return nombres
else
return ''
return ''
end
end
Ligne 150 : Ligne 255 :
if toParse ~= '' then
if toParse ~= '' then
local result
local result
local specificArgs = { ['à'] = 'à', et = 'et', ['–'] = '–', ['±'] = '±' }
local specificArgs = {
['à'] = 'à',
et = 'et',
ou = 'ou',
['/'] = '/', [';'] = '/',
['//'] = '//',
['–'] = '–', ['—'] = '–', ['-'] = '–',  -- demi cadratin, cadratin et tiret
['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
['+'] = '+',
['−'] = '−', -- signe moins
['×'] = '×', x = '×', ['*'] = '×',
['××'] = '××', xx = '××', ['**'] = '××',
}
 
-- valeur numérique
-- valeur numérique
local match, capture = toParse:match( '^((%-?%f[%d.,][%d., \194\160]*%d%f[%D])%s*)' )
local cap0, capture = toParse:match( '^(([%d., ]+%f[^d%(])%s*)' )
if match then
local prefix
toParse = toParse:sub( match:len() + 1 )
if not cap0 then
-- cas d'un nombre entre guillemet, gras, italique...
cap0, capture = toParse:match( '^((["\']+[%d., ]+["\']+)%s*)' )
end
if not cap0 then
-- cas ou le nombre est remplcé par un ou plusieurs points d'interrogation
cap0, prefix = toParse:match( '^((%?+)%s*)' )
end
if not cap0 then
-- cas ou un mot type "vers", "environ" précède le nombre (mot simple, sans accent pour ne pas complexifier pour des cas minoritaires)
cap0, prefix, capture = toParse:match( '^(([%a ]+[.,]?[: ]* )([+-]? ?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
end
if not cap0 then
-- cas ou le nombre est précédé par un signe, un symbole ASCII, ou suivit d'une incerititude entre parenthèse
cap0, prefix, capture = toParse:match( '^(([(<>=~ ]*)([+-]? ?%f[%d.,][%d., ]*%d%(?[%d%.]*%)?)%s*)' )
end
if not cap0 then
-- cas ou le nombre est précédé par un symbole ≤, ≥, ≈, ≃ et quelque autres
cap0, prefix, capture = toParse:match( '^((\226[\136\137][\131\136\164\165\187\188] ?)([+-]?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
end
if not cap0 then
-- cas ou le nombre est précédé par un symbole ± (\194\177)
cap0, prefix, capture = toParse:match( '^((±) ?(%f[%d.,][%d., ]*%d%f[%D])%s*)' )
end
result = { capture or false, prefix = prefix }
 
if cap0 then
toParse = toParse:sub( cap0:len() + 1 )
 
-- point de suspensions (ex π = 3.14159...)
cap0 = toParse:match( '^…%s*' )
if not cap0 then
cap0 = toParse:match( '^%.%.%.%s*' )
end
if cap0 then
result[1] = result[1] .. '…'
toParse = toParse:sub( cap0:len() + 1 )
end
if toParse == '' then
return result
end
end
 
-- fraction
capture = mw.ustring.sub( toParse, 1, 1 )
if fractionUnicode[ capture ] then
result.fraction = fractionUnicode[ capture ]
toParse = toParse:sub( capture:len() + 1 ):gsub( '^%s*', '' )
result[1] = result[1] or ''
else
cap0, capture = toParse:match( '^(([%d,]*/%f[%d][%d ]*%d)%s*)' )
if not cap0 then
-- caractère de fraction ⁄ = \226\129\132
cap0, capture = toParse:match( '^((%d*⁄%d+)%s*)' )
if cap0 then
capture = capture:gsub( '⁄', '/' )
end
end
if cap0 then
if result[1] and capture:match( '^/' ) then
local n = result[1]:match( ' %d+$' ) or result[1]:match( '^%d+$' )  or ''
result[1] = result[1]:sub( 1, -1 - #n )
result.fraction = n:gsub( '^ ', '' ) .. capture
else
result.fraction = capture
end
toParse = toParse:sub( cap0:len() + 1 )
end
end
 
if toParse~= '' and ( result[1] or result.fraction ) then
-- lien avec un deuxième nombre
local cap0, conj, num = toParse:match( '^(([etou+/;x*-]+) *(%-?%f[%d.,][%d., ]*%d%f[%D]%)?)%s*)' )
if not cap0 and toParse:byte() > 127 then
cap0, conj, num = mw.ustring.match( toParse, '^(([à−×±—–]+) *(%-?%f[%d.,][%d., ]*%d%f[%D]%)?)%s*)' )
end
if cap0 and specificArgs[ conj ]
and not (
specificArgs[ conj ] == '×'
and (
mw.ustring.match( toParse, '^[×x] ?10 ?e' )
or mw.ustring.match( toParse, '^[×x] ?10<sup>(%-?%d+)</sup>' )
)
)
then
result[ specificArgs[ conj ] ] = num
toParse = toParse:sub( cap0:len() + 1 )
end
if result['+'] or result['×'] or result['/'] then
cap0, conj, num = mw.ustring.match( toParse, '^(([/;x*×−-]) *(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
if cap0 then
if specificArgs[ conj ] == '×' then
result['××'] = num
elseif specificArgs[ conj ] == '/' then
result['//'] = num
else
result['−'] = num
end
toParse = toParse:sub( cap0:len() + 1 )
end
end
end
end
result = { capture or false }
 
-- 10 exposant  ( \195\151 = ×, signe multiplié)
-- 10 exposant  ( \195\151 = ×, signe multiplié)
match, capture = toParse:match( '^(%s*e(%-?%d+)%s*)' )
cap0, capture = toParse:match( '^(e(%-?%d+)%s*)' )
if not match then
if not cap0 then
match, capture = toParse:match( '^(%s*[x\195]\151?10e(%-?%d+)%s*)' )
cap0, capture = toParse:match( '^([x\195]\151? ?10e(%-?%d+)%s*)' )
end
if not cap0 then
cap0, capture = toParse:match( '^([x\195]\151? ?10<sup>(%-?%d+)</sup>%s*)' )
end
end
if match then
if cap0 then
result.e = capture
result.e = capture
toParse = toParse:sub( match:len() + 1 )
toParse = toParse:sub( cap0:len() + 1 )
end
if result[1] == '10' and not result.e and not result.fraction then
cap0, capture = toParse:match( '^(<sup>(%-?%d+)</sup>%s*)' )
if cap0 then
result[1] = false
result.e = capture
toParse = toParse:sub( cap0:len() + 1 )
end
end
 
if toParse == '' then
return result
end
end
-- unités
-- unités
toParse = toParse:gsub( '', '.' )
local texteUnit = toParse
local unit, exp
toParse = toParse:gsub( '^([^%[<]-)<sup>(%d)</sup>', '%1%2' )
local unitRegex = '^(%.?(/?[%aà°±]+) ?(%-?%d*)%s*)'
if Data.unit[ toParse ]
match, unit, exp = mw.ustring.match( toParse, unitRegex )
or toParse:match( '%b<>' )
while match and unit:len() < 5 do
or  toParse:match( 'UNIQ%-%-%a+%-%x%x%x%x%x%x%x%x%-QINU' )
if specificArgs[ unit ] then
or mw.ustring.match( toParse, '^%a+$' )  
result[ specificArgs[ unit ] ] = exp
then
else
result[ #result + 1] = toParse
table.insert( result, unit )
toParse = ''
table.insert( result, exp )
elseif toParse:match( '%b<>' ) then
end
toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
toParse = toParse:sub( match:len() + 1 )
end
match, unit, exp = mw.ustring.match( toParse, unitRegex )
if toParse ~= '' then
local unit, exp
toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
repeat
-- unité contenant un lien
cap0, unit, exp = mw.ustring.match( toParse, '^((/? ?[^%s%d/%[%]]*%b[][^%s%d/]*) ?(%-?%d*)%s*)' )
if not cap0 then
-- unité ne contenant pas de lien
cap0, unit, exp = mw.ustring.match( toParse, '^((/? ?[^%s%d/]+) ?(%-?%d*)%s*)' )
end
if not cap0 then
-- l/100 km
cap0, unit, exp = mw.ustring.match( toParse, '^((/100 ?[^%s%d/]+) ?(%-?%d*)%s*)' )
end
if cap0 then
if unit:match( '%-$' ) and exp ~= '' then -- rustine pour quand le "-" se retrouve dans la capture "unit" au lieu de la capture "exp"
unit = unit:gsub( '%-$', '' )
exp = '-' .. exp
elseif exp == '-' then -- rustine pour quand un "-" a été capturé dans "exp" mais sans qu'il y ait de chiffres après
unit = cap0
exp = ''
end
if Data.unit[ unit ] or mw.ustring.match( unit, '[%a€£$¥«»]' ) then
result[ #result + 1] = unit
result[ #result + 1] = exp
toParse = toParse:sub( cap0:len() + 1 )
else
break
end
end
until toParse == '' or not cap0
end
end
if toParse == '' then
if toParse == '' then
if #result > 3 then
local estSimpleTexte = true
for r = 2, #result, 2 do
if Data.unit[ result[ r ] ]
or result[ r ]:sub( 1, 1 ) == '/'
or Data.prefix[ result[ r ]:sub( 1, 1 ) ] and Data.unit[ result[ r ]:sub( 2 ) ]
or Data.prefix[ result[ r ]:sub( 1, 2 ) ] and Data.unit[ result[ r ]:sub( 3 ) ]
or result[ r + 1 ] and result[ r + 1 ] ~= ''
then
estSimpleTexte = false
break
end
end
if estSimpleTexte then
result[ 2 ] = texteUnit
for r = #result, 3, -1 do
result[ r ] = nil
end
end
end
if #result > 1 and result[ #result ] == '' then
result[ #result ] = nil
end
return result
return result
else
else
-- une partie de la chaine n'a pas pu être décodée, on retourne la chaine originale
-- une partie de la chaine n'a pas pu être décodée, on retourne la chaine originale
addErrorCat = true
return { texte }
return { texte }
end
end
Ligne 196 : Ligne 482 :


---
---
-- nomUtnit retourne le nom français du code d'une unité et de son exposant.
-- nomUnit retourne le nom français du code d'une unité et de son exposant.
-- si le code de l'unité n'est pas reconnu retourne 1 et false, de façon à ajouter false en première position d'une table.  
-- si le code de l'unité n'est pas reconnu, retourne false.
function p.nomUnit( unit, exp )
function p.nomUnit( unit, exposant )
unit = trim( unit )
if not dataSuccess or type( unit ) ~= 'string' then
if not dataSuccess or type( unit ) ~= 'string' then
return 1, false
return false
end
end
-- nettoyage des liens et balise HTML
unit = unit:gsub( '^/' , '' )
unit = unit:gsub( '^/' , '' )
if unit:match( '%[' ) then
local Delink = require( 'Module:Delink' )
unit = Delink._delink{ unit }
end
if unit:match( '<' ) then
unit = unit:gsub( '%b<>', '' )
end
-- /100
local divisor = ''
if unit:sub( 1, 2 ) == '10' then
divisor, unit = unit:match( '^(1[0 ]*)(.+)$' )
local divisorName = {
['10'] = 'dix ',
['100'] = 'cent ',
['1000'] = 'mille ',
['10000'] = 'dix-mille ',
['100000'] = 'cent-mille ',
['1000000'] = 'un million de ',
['1000000000'] = 'un millard de ',
}
divisor = divisorName[ divisor:gsub( ' ', '' ) ]
end
-- récupère le nom de l'unité
local unitTab = Data.unit[ unit ]
local unitTab = Data.unit[ unit ]
local unitPrefix = { nom = '' }
local unitPrefix = { nom = '' }
Ligne 209 : Ligne 522 :
unitPrefix = Data.prefix[ unit:sub( 1, 1 ) ]
unitPrefix = Data.prefix[ unit:sub( 1, 1 ) ]
if not ( unitTab and unitPrefix ) then
if not ( unitTab and unitPrefix ) then
-- pour µ, Ki, Mi, Gi... qui sont codé sur deux octets
unitTab = Data.unit[ unit:sub( 3 ) ]
unitTab = Data.unit[ unit:sub( 3 ) ]
unitPrefix = Data.prefix[ unit:sub( 1, 2 ) ]
unitPrefix = Data.prefix[ unit:sub( 1, 2 ) ]
Ligne 216 : Ligne 530 :
end
end
end
end
-- récupère le nom de l'exposant
if trim( exposant ) then
local exp = tonumber( exposant )
exp = exp and Data.exposant[ math.abs( exp ) ]
exposant = exp or ' puissance ' .. exposant
else
exposant = ''
end
-- assemble les deux partie
if type( unitTab ) == 'table' and type( unitTab.nom ) == 'string' then
if type( unitTab ) == 'table' and type( unitTab.nom ) == 'string' then
exp = tonumber( exp )
return divisor .. unitPrefix.nom .. unitTab.nom .. exposant
exp = exp and math.abs( exp )
elseif unit:match( '[/%d]' ) then
local exposant = exp and Data.exposant[exp] or ''
-- ce n'est pas du texte simple, on anule l'infobule
return unitPrefix.nom .. unitTab.nom .. exposant  
return false
else
else
return 1, false
return unit .. exposant
end
end
end
end


function p._unite( args )
function p._unite( args )
local sep = ''
-- formatage du nombre
local nombre, nbNombre, nombre2, nbNombre2
local nombre = p.formatNombres( args[1], args.arrondi, args['décimales'] )
nombre = p.sanitizeNum( args[1] )
if nombre == '' then
if nombre == '' then
nombre = nil
nombre = nil
else
-- formatage du nombre
nombre, nbNombre = nombre:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre )
end
end
 
-- et / à '–'
local wiki = {}
local intervalle = trim( args.et ) and ' et '
 
or trim( args['à'] ) and ' à '
-- prefix est un paramètre interne défini par p.parseUnit, utile notamment lorsque {{unité}} est utilisé dans les infobox
or args['–'] and '–'
if args.prefix then
if nombre and intervalle then
wiki[ #wiki + 1 ] = args.prefix
nombre2 = nombre2 or trim( args['à'] ) or trim( args.et ) or trim( args['–'] )
end
if nombre2 then  
 
nombre2 = p.sanitizeNum( nombre2 )  
if nombre then
nombre2, nbNombre2 = nombre2:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre )
wiki[ #wiki + 1 ] = nombre
if intervalle == '–' and nombre:sub( 1, 2 ) == '' then
end
intervalle = ' '
 
-- fraction
local fraction = args.fraction
if fraction then
fraction = fractionUnicode[ fraction ] or fraction
local nom, den = fraction:match( '^(.-)/(.+)$' )
if nom then
if nom:match( '^[%dn]%d?$' ) and den:match( '^[%daeoxhklmnpst]$' ) then
nom = nom:gsub( '[%dn()=+-]', supUnicode )
den = den:gsub( '[%daeoxhklmnpst()=+-]', subUnicode )
else
nom = '<sup style="font-size: 70%; vertical-align: 0.4em;">'
.. p.formatNombres( nom )
.. '</sup>'
den = '<sub style="font-size: 70%; vertical-align: 0em;">'
.. p.formatNombres( den )
.. '</sub>'
end
end
fraction = nom .. '⁄' .. den
end
end
if nombre then
wiki[ #wiki + 1 ] = nbsp
end
wiki[ #wiki + 1 ] = fraction
end
end
 
-- ±
-- à, et, ou, ×, – (tiret cadratin)
local approximation
local specificArgs = { '–', 'à', 'et', 'ou', '/', '//', '×', '××', '±' }
if trim( args['±'] ) then
for i = 1, #specificArgs do
approximation = '±\194\160' .. p.formatNombre( args['±'] )
local name = specificArgs[ i ]
end
local v = args[ name ] and trim( args[ name ] )
if v then
-- puissance de 10
v = p.formatNombres( v )
local exposant = trim( args.e )
if name == '//' then
if exposant then
name = '/'
if nombre then
elseif name == '××' then
exposant = '×\194\16010<sup>' .. exposant .. '</sup>'
name = '×'
else
end
exposant = '10<sup>' .. exposant .. '</sup>'
if name == '–' and nombre and nombre:match( '^[^−]' ) and v:match( '^[^−]' ) then
-- pas d'espace pour le tiret cadratin entre deux nombres positifs
wiki[ #wiki + 1 ] = '–'
elseif name == '×' or name == '±' then
wiki[ #wiki + 1 ] = nbsp .. name .. nbsp
else
wiki[ #wiki + 1 ] = nbsp .. name .. ' '
end
wiki[ #wiki + 1 ] = v
end
end
end
end
 
-- unités
-- analyse de l'unité pour la conversion (mais ne sera affiché qu'après l'incertitude + et - séparé)
local i = 1
local i = 1
local unit = trim( args[ 2 * i ] )
local unit = trim( args[ 2 * i ] )
Ligne 277 : Ligne 627 :
local exp = p.parseNombre( args[ 2 * i + 1 ] )
local exp = p.parseNombre( args[ 2 * i + 1 ] )
local sep = ''
local sep = ''
if units ~= '' then
-- gestion des exposants
if unit:sub( 1, 1 ) == '/' then
local expUnit = ''
if not par then
if exp == '' then
par = true
local suffix = unit:sub( -2 ) -- yes, it's 2 bytes
table.insert( nomUnits, 'par' )
if suffix == '²' then
exp = '2'
unit = unit:sub( 1, -3 )
elseif suffix == '³' then
exp = '3'
unit = unit:sub( 1, -3 )
end
end
if #exp > 0 then
expUnit = '<sup>' .. exp:gsub( '^-', '−') .. '</sup>'  -- remplace le tiret par un vrai signe moins
end
-- gestion de la séparation des unités et des unités en dénominateur
if unit:sub( 1, 1 ) == '/' then
sep = '/'
unit = trim( unit:sub( 2 ) ) or ''
if not par then
par = true
if unit:sub( 1, 2 ) == '10' then
nomUnits[ #nomUnits + 1 ] = 'pour'
else
nomUnits[ #nomUnits + 1 ] = 'par'
end
end
else
else
sep = '\194\160'  -- point médian désactivé : '⋅\194\160' 
nomUnits[ #nomUnits + 1 ] = 'et par'
if exp:match( '^-' ) and not par then
if nomUnits[ #nomUnits - 2 ] == 'et par' then
par = true
nomUnits[ #nomUnits - 2 ] = 'par'
table.insert( nomUnits, 'par' )
end
end
end
end
elseif units ~= '' then
sep = nbsp
end
end
local expUnit = ''
if exp:match( '^-' ) and not par then
if #exp > 0 then
par = true
expUnit = '<sup>' .. exp:gsub( '^-', '−') .. '</sup>'  -- remplace le tiret par un vrai signe moins
nomUnits[ #nomUnits + 1 ] = 'par'
end
-- remplacement de l'unité par son symbole
if Data.unit[ unit ] then
-- unit = Data.unit[ unit ].symbole
-- désactivé car ne gère pas les multiple tel mL
end
end
units = units .. sep .. unit .. expUnit
units = units .. sep .. unit .. expUnit
table.insert( nomUnits, p.nomUnit( unit, exp ) )
local nomUnit = p.nomUnit( unit, exp )
if nomUnit then
nomUnits[ #nomUnits + 1 ] = nomUnit
else
-- si le code de l'unité n'est pas reconnu, insère false en première position de la table.
table.insert( nomUnits, 1, false )
end
i = i + 1
i = i + 1
unit = trim( args[ 2 * i ] )
unit = trim( args[ 2 * i ] )
end
end
 
if units == '°' then
local unitFullName = nomUnits[1] and table.concat( nomUnits, ' ' ) or false
nombre = nombre and nombre .. '°'
 
nombre2 = nombre2 and nombre2 .. '°'
-- conversion
approximation = approximation and approximation .. '°'
if unitFullName then
units = nil
local nameSingular = mw.ustring.gsub( unitFullName, '(%a)s%f[%A]', '%1' )
end  
local multiple = 1
if intervalle then
local convertTable = Data.convert[ nameSingular ]
nombre = nombre .. intervalle .. nombre2
if not convertTable and #nameSingular > 5 then
-- gesion des multiples (Kilo, méga, mili...)
local prefix = Data.prefix[ nameSingular:sub( 1, 4 ) ] or Data.prefix[ nameSingular:sub( 1, 5 ) ]
local _, par = nameSingular:find( ' par ' )
local prefix2
if par then
prefix2 = Data.prefix[ nameSingular:sub( par + 1, 4 ) ] or Data.prefix[ nameSingular:sub( par + 1, 5 ) ]
end
if prefix and Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ) ] then
convertTable = Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ) ]
multiple = 10 ^ prefix.puissance
elseif prefix2 and Data.convert[ nameSingular:gsub( ' par ' .. prefix2.nom, '' ) ] then
convertTable = Data.convert[ nameSingular:gsub( ' par ' .. prefix2.nom, '' ) ]
multiple = 1 / 10 ^ prefix2.puissance
elseif prefix and prefix2 and Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ] then
convertTable = Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ]
multiple = 10 ^ prefix.puissance / 10 ^ prefix2.puissance
end
end
if convertTable then
if type( convertTable[1] ) ~= 'table' then
convertTable = { convertTable }
end
for i = 1, #wiki do
local v = wiki[ i ]
local n = tonumber( p.parseNombre( v ) )
if n then
n = n * 10 ^ ( tonumber( p.parseNombre( args.e ) ) or 0 )
local converted = {}
for _, c in ipairs( convertTable ) do
local nConverted = n
if c.inverse then
nConverted = 1 / n
end
if c.M then
-- M = masse molaire
local M = tonumber( args.M )
if not M then
break
end
if c.M == '*' then
nConverted = nConverted * M
elseif c.M == '/' then
nConverted = nConverted / M
end
end
nConverted = nConverted * multiple * c[2] + ( c[3] or 0 )
-- format
nConverted = p.formatNum{ nConverted, round = c.round or 6, noHtml = true }
local sep = ' '
if c[1]:sub( 1, 2 ) == '°' then -- yes, it's 2 bytes
sep = ''
end
converted[ #converted + 1 ] = nConverted .. sep.. c[1]
end
wiki[ i ] = '<span title="' .. table.concat( converted, ' ou ' ) ..'">' .. v ..'</span>'
end
end
end
end
 
-- incertitude avec + et − séparés
if trim( args['+'] ) then
local approximation = '+' .. p.formatNombre( args['+'] ) .. ''
if trim( args['−'] ) then
approximation = approximation .. '<br> −' .. p.formatNombre( args['−'] )
end
wiki[ #wiki + 1 ] = '<span class="nowrap"><span style="display:inline-block; padding-left:0.2em; vertical-align:top; line-height:1em; font-size:80%; text-align:left;">'
wiki[ #wiki + 1 ] = approximation .. '</span></span>'
end
end
 
local wiki = { nombre }
-- puissance de 10
table.insert( wiki, approximation )  
local exposant = trim( args.e )
table.insert( wiki, exposant )
if exposant then
if nomUnits[1] then
exposant = p.formatNombre( exposant )
units = string.format( '<abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
if nombre then
if trim( args['±'] ) and not nombre:match( '^%(' ) then
table.insert( wiki, 1, '(' )
wiki[ #wiki + 1 ] = ')'
end
wiki[ #wiki + 1 ] = nbsp .. '×' .. nnbsp .. '10<sup>' .. exposant .. '</sup>'
else
wiki[ #wiki + 1 ] = '10<sup>' .. exposant .. '</sup>'
end
end
end
table.insert( wiki, units )
 
if units ~= '' then
if #wiki > 0 then
local sep = nbsp
local result = table.concat( wiki, ' ' )
if not ( nombre or args.fraction or exposant ) then
if result:match( ' ' ) then
sep = ''
return '<span class="nowrap">' .. result .. '</span>'
else
else
return result
local symbole = Data.unit[ units ] and Data.unit[ units ].symbole
if symbole == '°' or symbole == '′' or symbole == '″' then
sep = ''
end
end
-- ajoute une abréviation si le nom de l'unité est différent de l'unité (en considérant les espaces qui peuvent être devenus insécables)
if unitFullName and unitFullName ~= units:gsub( nbsp, ' ' ) then
units = string.format( '<abbr class="abbr" title="%s">%s</abbr>', unitFullName, units )
end
end
wiki[ #wiki + 1 ] = sep .. units
end
if #wiki > 0 then
return table.concat( wiki )
end
end
end
end
Ligne 331 : Ligne 800 :
function p.unite( frame )
function p.unite( frame )
local args
local args
if type( frame ) == 'table' and type( frame.getParent ) == 'function' then
if type( frame ) == 'table' then
args = frame:getParent().args;
if type( frame.getParent ) == 'function' then
args = frame:getParent().args
else
args = frame
end
end
end
if args then
if args then
if not args[2]  
addErrorCat = false
and not ( args.e or args.et or args['à'] or args['±'] )
args[1] = trim( args[1] ) or false
and trim( args[1] )  
local basique = require( 'Module:Yesno' )( args.basique )
and args[1]:match('[^%d,. -]')  
if args[1] and not basique then
then
if args[1]:match('[^%d,. -]') then
args = p.parseUnit( trim( args[1] ) )
local tempArgs = p.parseUnit( args[1] )
if not ( args[2] and tempArgs[2] ) then
for k, v in pairs( tempArgs ) do
args[k] = v
end
end
end
args[2] = trim( args[2] ) or false
if args[2] and not args[3] then
-- cas ou le paramètre 2 contient 'm3' ou 'km2'
local a, d = args[2]:match('^(%a%a?)(%d)$')
if a and Data.unit[a] then
args[2] = a
args[3] = d
end
-- cas ou le paramètre 2 contient 'km/s' ou 'm3/h'
if args[2]:match('/') then
local tempArgs = p.parseUnit( args[2] )
args[2] = false
if tempArgs[1] ~= false then
table.insert( tempArgs, 1, false )
end
for k, v in pairs( tempArgs ) do
if args[k] and v then
addErrorCat = true
end
args[k] = args[k] or v
end
end
end
end
-- args alias
args['×'] = args['×'] or args['x']  -- lettre x → signe multiplié
args['±'] = args['±'] or args['+-'] or args['+/-']
if args['+'] then
args['−'] = args['−'] or args['-'] -- tiret → signe moins
else
args['–'] = args['–'] or args['-'] -- tiret → demi-cadratin
end
local cat = ''
if addErrorCat and mw.title.getCurrentTitle():inNamespaces( 0, 4, 8, 10, 12, 14, 100, 828 ) then
cat = errorCat
mw.log( errorCat ,' → ', args[1], '|', args[2] )
end
end
return p._unite( args )
return ( p._unite( args ) or '' ) .. cat
end
end
end
function p.emulationFormatnum( frame )
local args = frame:getParent().args
return p.formatNombres( args[1] )
end
end


return p
return p

Dernière version du 30 juillet 2023 à 16:18

La documentation pour ce module peut être créée à Module:Unité/doc

local p = {}

-- local Delink = require( 'Module:Delink' ) -- chargé uniquement si nécessaire

-- Chargement de la base de données des nom d'unités avec gestion d'erreur.
local moduleData = 'Module:Unité/Data'
local dataSuccess, Data = pcall ( mw.loadData, moduleData )
if dataSuccess and type( Data ) == 'table' then
	dataSuccess = type( Data.unit ) == 'table'
		and type( Data.prefix ) == 'table'
		and type( Data.exposant ) == 'table'
end

local errorCat = '[[Catégorie:Page incorrectement traitée par le Module:Unité]]'
local addErrorCat = false

local supUnicode = { ['0'] = '⁰', ['1'] = '¹', ['2'] = '²', ['3'] = '³', ['4'] = '⁴', ['5'] = '⁵', ['6'] = '⁶', ['7'] = '⁷', ['8'] = '⁸', ['9'] = '⁹',
	['+'] = '⁺', ['-'] = '⁻', ['='] = '⁼', ['('] = '⁽', [')'] = '⁾', ['n'] = 'ⁿ' }
local subUnicode = { ['0'] = '₀', ['1'] = '₁', ['2'] = '₂', ['3'] = '₃', ['4'] = '₄', ['5'] = '₅', ['6'] = '₆', ['7'] = '₇', ['8'] = '₈', ['9'] = '₉',
	['a'] = 'ₐ', ['e'] = 'ₑ', ['o'] = 'ₒ', ['x'] = 'ₓ', ['h'] = 'ₕ', ['k'] = 'ₖ', ['l'] = 'ₗ',
	['m'] = 'ₘ', ['n'] = 'ₙ', ['p'] = 'ₚ', ['s'] = 'ₛ', ['t'] = 'ₜ',
	}
local fractionUnicode = { ['½'] = '1/2', ['⅓'] = '1/3', ['⅕'] = '1/5', ['⅙'] = '1/6', ['⅛'] = '1/8', 
	['⅔'] = '2/3', ['⅖'] = '2/5', ['⅚'] = '5/6', ['⅜'] = '3/8', ['¾'] = '3/4', ['⅗'] = '3/5', 
	['⅝'] = '5/8', ['⅞'] = '7/8', ['⅘'] = '4/5', ['¼'] = '1/4', ['⅐'] = '1/7', ['⅑'] = '1/9', ['⅒'] = '1/10', ['↉'] = '0/3', 
}
local nbsp = '\194\160'        -- espace insécable
local nnbsp = '\226\128\175'   -- espace fine insécable

--- Copie de Outils.trim acceptant les nombres.
local function trim( texte )
	if type( texte ) == 'string' then
		-- http://lua-users.org/wiki/StringTrim
		texte = texte:match( '^()%s*$' ) and '' or texte:match( '^%s*(.*%S)' )
		if texte ~= '' then
			return texte
		end
	elseif type( texte ) == 'number' then
		return tostring( texte )
	end
end

-- retire les chiffres des strip markers
local function escapeStripMarkers( input )
	return input:gsub( '(UNIQ%-%-%a+%-)(%x%x%x%x%x%x%x%x)(%-QINU)', function ( leading, hexdigits, trailing )
		local escapeddigits = hexdigits:gsub( '%d', {
			['0'] = 'g', ['1'] = 'h', ['2'] = 'i', ['3'] = 'j', ['4'] = 'k',
			['5'] = 'l', ['6'] = 'm', ['7'] = 'n', ['8'] = 'o', ['9'] = 'p',
		} )
		return leading .. escapeddigits .. trailing
	end )
end

-- restaure les strip markers
local function restoreStripMarkers( input )
	return input:gsub( '(UNIQ%-%-%a+%-)(%a%a%a%a%a%a%a%a)(%-QINU)', function ( leading, escapeddigits, trailing )
		local hexdigits = escapeddigits:gsub( '%a', {
			['g'] = '0', ['h'] = '1', ['i'] = '2', ['j'] = '3', ['k'] = '4',
			['l'] = '5', ['m'] = '6', ['n'] = '7', ['o'] = '8', ['p'] = '9',
		} )
		return leading .. hexdigits .. trailing
	end )
end

-- remplacement de certains caractères, pour simplifier les pattern
function p.sanitizeNum( nombre )
	if type( nombre ) == 'number' then
		return tostring( nombre )
	elseif type( nombre ) == 'string' then
		if nombre:match( '^%-?[%d.,]+$' ) then
			return nombre
		end
		local result = nombre
			-- remplacement des signes moins par un tiret
			:gsub( '%−%f[%d]', '-')  -- U+2212
			:gsub( '&minus;%f[%d]', '-')  -- html &minus;
			-- remplacement des espaces insécable par des espace simple
			:gsub( nbsp, ' ' )
			:gsub( '&nbsp;', ' ' )
			:gsub( '&#160;', ' ' )
			:gsub( nnbsp, ' ' )
			:gsub( '&#8239;', ' ' )
			:gsub( '&#x202F;', ' ' )
			:gsub( '\226\128[\132-\138]', ' ' ) -- U+2004 à U+200A
			:gsub( '&#32;', ' ' )
			-- trim
			:gsub( '^%s*(%S?.-)%s*$', '%1' )
		return result
	else
		return ''
	end
end

---
-- parseNum transforme si possible une chaine formatée en un chaine interprétable par tonumber()
-- retourne une chaine pour éviter les arrondi éventuels de lua.
-- si "nombre" est une chaine non reconnue comme un nombre par la fonction, retourne "nombre".
-- si "nombre" n'est pas un number ou une chaine retourne une chaine vide.
function p.parseNombre( nombre )
	local result
	if type( nombre ) == 'number' then
		return tostring( nombre )
	else
		-- remplacement des signes moins ou demi-cadratin par un tiret
		result = p.sanitizeNum( nombre )
		if result == '' then
			return ''
		end
		-- si nombre est un chiffre en exposant ou indice comme ², retourne ce chiffre
		for i = 0, 9 do
			local is = tostring(i)
			if result == supUnicode[ is ] or result == subUnicode[ is ] then
				return is
			end
		end
		if not result:match( '^%-?[%d., ]*%d$' ) and not result:match( '^%-?[%d., ]*%d ?e[+-]?%d+$' ) then
			return nombre
		end
	end

	-- suppression espaces
	result = result:gsub( ' ', '' )

	-- gestion des points et des virgules
	if result:match( '[.,]' ) then
		if result:match( '%d%.%d%d%d%.%d' ) then
			-- type 12.345.678
			result = result:gsub( '%.', '' ):gsub( ',', '.' )
		elseif result:match( '%d,%d%d%d,%d' ) -- type 1,234,567 ou 1.234,567,8
			or result:match( '%d,%d%d%d%.%d' )  -- format anglo-saxon type 1,234.5
			or result:match( '%d%.%d%d%d,%d' ) -- type 1.123,56 (utilisé en exemple pour sépararer les décimales avec l'ancien modèle unité ou formatnum)
		then
			result = result:gsub( ',', '' )
		else
			result = result:gsub( ',', '.' )
		end
	end
	
	return result
end

---
-- _formantNum transforme un nombre ou une chaine représentant un nombre en chaine formatée suivant les conventions du français
-- si le paramètre ne représente pas un nombre lua il est retourné sans modification
-- Le paramètre peut être transmis sous forme de table pour ajouter des options :
-- * round : arrondi à n chiffre après la virgule (peut être négatif)
-- * decimals : nombre de décimales affichées (peut être négatif, dans ce cas équivalent à round)
-- * noHtml : n'utilise pas de balise HTML pour affiché les puissance de 10 (pour pouvoir être utilisé en title)
function p.formatNum( num )
	local params = {}
	if type( num ) == 'table' then
		params = num
		num = params[1]
	end
	if type( num ) == 'number' then
		num = tostring( num )
	elseif type( num ) ~= 'string' or num == '' then
		return num
	end

	-- séparation exposant
	local n, exponent = num:match( '^([-%d.]+)[eE]([+-]?%d+)$' )
	if exponent then
		num = n
		if params.noHtml then
			exponent = exponent:gsub('+?%f[%d]0', '' )
				:gsub( '[%d-]', supUnicode )
		else
			exponent = '<sup>' .. exponent:gsub('^%+?(%-?)0?', { ['-'] = '−', [''] = '' } ) .. '</sup>'
		end
		if num == '1' then
			return '10' .. exponent
		end
		exponent = nbsp .. '×' .. nnbsp .. '10' .. exponent
	else
		exponent = ''
	end

	-- arrondi
	local decimals = tonumber( params.decimals )
	local round = tonumber( params.round ) or decimals
	if round and tonumber( num ) then
		local mult = 10 ^ round
		num = tostring( math.floor( num * mult + 0.5 ) / mult )
	end

	local moins, entier, deci = num:match( '^(%-?)(%d*)%.?(%d*)$' )
	if not entier then
		return num
	end

	if moins == '-' then
		moins = '−' -- signe moins (U+2212)
	end

	if entier == '' then
		entier = '0'
	elseif entier:len() > 3 then
		local ini = math.fmod( entier:len() - 1, 3 ) + 1
		entier = ( entier:sub( 1, ini ) or '') .. entier:sub( ini + 1 ):gsub( '(%d%d%d)', nbsp .. '%1' )
	end
	if deci ~= '' or ( decimals and decimals > 0 ) then
		if decimals and decimals > #deci then
			deci = deci .. string.rep( '0', decimals - #deci )
		end
		if #deci > 3 then
			deci = ',' .. deci:gsub( '(%d%d%d)', '%1' .. nbsp ):gsub( nbsp .. '$', '' )
		else
			deci = ',' .. deci
		end
	end

	return moins .. entier .. deci .. exponent
end

---
-- formatNombre transforme un nombre formaté ou non en chaine formatée suivant les convention du français.
-- si la chaine n'est pas reconnu comme un nombre, elle n'est pas modifiée.
function p.formatNombre( num, round, decimals )
	return p.formatNum{ p.parseNombre( num ), round = round, decimals = decimals }
end

--- formatNombres transforme tous les nombres d'une chaine en nombre formaté suivant les conventions du français.
function p.formatNombres( nombres, round, decimals )
	if type( nombres ) == 'number' then
		return p.formatNum{ nombres, round = round, decimals = decimals }
	elseif type( nombres ) == 'string' then
		-- retire les chiffres des strip markers
		nombres = escapeStripMarkers( nombres )

		-- formatage proprement dit
		nombres = p.sanitizeNum( nombres )
		local formatN = function ( n )
			return p.formatNombre( n, round, decimals )
		end
		if nombres:match('%d%-%d') then
			nombres = nombres:gsub( '%f[%d.,][%d., ]*%d', formatN )
		else
			nombres = nombres
				:gsub( '%-?%f[%d.,][%d., ]*%d ?e[+-]?%d+', formatN )
				:gsub( '%-?%f[%d.,][%d., ]*%d', formatN )
		end

		-- restaure les strip markers
		nombres = restoreStripMarkers( nombres )

		return nombres
	else
		return ''
	end
end

function p.parseUnit( texte )
	local toParse = p.sanitizeNum( texte )
	if toParse ~= '' then
		local result
		local specificArgs = {
			['à'] = 'à',
			et = 'et',
			ou = 'ou',
			['/'] = '/', [';'] = '/',
			['//'] = '//',
			['–'] = '–', ['—'] = '–', ['-'] = '–',  -- demi cadratin, cadratin et tiret
			['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
			['+'] = '+',
			['−'] = '−', -- signe moins
			['×'] = '×', x = '×', ['*'] = '×',
			['××'] = '××', xx = '××', ['**'] = '××',
		}

		-- valeur numérique
		local cap0, capture = toParse:match( '^(([%d., ]+%f[^d%(])%s*)' )
		local prefix
		if not cap0 then
			-- cas d'un nombre entre guillemet, gras, italique...
			cap0, capture = toParse:match( '^((["\']+[%d., ]+["\']+)%s*)' )
		end
		if not cap0 then
			-- cas ou le nombre est remplcé par un ou plusieurs points d'interrogation
			cap0, prefix = toParse:match( '^((%?+)%s*)' )
		end
		if not cap0 then
			-- cas ou un mot type "vers", "environ" précède le nombre (mot simple, sans accent pour ne pas complexifier pour des cas minoritaires)
			cap0, prefix, capture = toParse:match( '^(([%a ]+[.,]?[: ]* )([+-]? ?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
		end
		if not cap0 then
			-- cas ou le nombre est précédé par un signe, un symbole ASCII, ou suivit d'une incerititude entre parenthèse
			cap0, prefix, capture = toParse:match( '^(([(<>=~ ]*)([+-]? ?%f[%d.,][%d., ]*%d%(?[%d%.]*%)?)%s*)' )
		end
		if not cap0 then
			-- cas ou le nombre est précédé par un symbole ≤, ≥, ≈, ≃ et quelque autres
			cap0, prefix, capture = toParse:match( '^((\226[\136\137][\131\136\164\165\187\188] ?)([+-]?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
		end
		if not cap0 then
			-- cas ou le nombre est précédé par un symbole ± (\194\177)
			cap0, prefix, capture = toParse:match( '^((±) ?(%f[%d.,][%d., ]*%d%f[%D])%s*)' )
		end
		result = { capture or false, prefix = prefix }

		if cap0 then
			toParse = toParse:sub( cap0:len() + 1 )

			-- point de suspensions (ex π = 3.14159...)
			cap0 = toParse:match( '^…%s*' )
			if not cap0 then
				cap0 = toParse:match( '^%.%.%.%s*' )
			end
			if cap0 then
				result[1] = result[1] .. '…'
				toParse = toParse:sub( cap0:len() + 1 )
			end
			if toParse == '' then
				return result
			end
		end

		-- fraction
		capture = mw.ustring.sub( toParse, 1, 1 )
		if fractionUnicode[ capture ] then
			result.fraction = fractionUnicode[ capture ]
			toParse = toParse:sub( capture:len() + 1 ):gsub( '^%s*', '' )
			result[1] = result[1] or '' 
		else
			cap0, capture = toParse:match( '^(([%d,]*/%f[%d][%d ]*%d)%s*)' )
			if not cap0 then
				-- caractère de fraction ⁄ = \226\129\132
				cap0, capture = toParse:match( '^((%d*⁄%d+)%s*)' )
				if cap0 then 
					capture = capture:gsub( '⁄', '/' )
				end
			end
			if cap0 then
				if result[1] and capture:match( '^/' ) then
					local n = result[1]:match( ' %d+$' ) or result[1]:match( '^%d+$' )  or ''
					result[1] = result[1]:sub( 1, -1 - #n )
					result.fraction = n:gsub( '^ ', '' ) .. capture
				else
					result.fraction = capture
				end
				toParse = toParse:sub( cap0:len() + 1 )
			end
		end

		if toParse~= '' and ( result[1] or result.fraction ) then
			-- lien avec un deuxième nombre
			local cap0, conj, num = toParse:match( '^(([etou+/;x*-]+) *(%-?%f[%d.,][%d., ]*%d%f[%D]%)?)%s*)' )
			if not cap0 and toParse:byte() > 127 then
				cap0, conj, num = mw.ustring.match( toParse, '^(([à−×±—–]+) *(%-?%f[%d.,][%d., ]*%d%f[%D]%)?)%s*)' )
			end
			if cap0 and specificArgs[ conj ]
				and not ( 
					specificArgs[ conj ] == '×' 
					and (
						mw.ustring.match( toParse, '^[×x] ?10 ?e' )
						or mw.ustring.match( toParse, '^[×x] ?10<sup>(%-?%d+)</sup>' )
					)
				) 
			then
				result[ specificArgs[ conj ] ] = num
				toParse = toParse:sub( cap0:len() + 1 )
			end
			if result['+'] or result['×'] or result['/'] then
				cap0, conj, num = mw.ustring.match( toParse, '^(([/;x*×−-]) *(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
				if cap0 then
					if specificArgs[ conj ] == '×' then
						result['××'] = num
					elseif specificArgs[ conj ] == '/' then
						result['//'] = num
					else
						result['−'] = num
					end
					toParse = toParse:sub( cap0:len() + 1 )
				end
			end
		end

		-- 10 exposant   ( \195\151 = ×, signe multiplié)
		cap0, capture = toParse:match( '^(e(%-?%d+)%s*)' )
		if not cap0 then
			cap0, capture = toParse:match( '^([x\195]\151? ?10e(%-?%d+)%s*)' )
		end
		if not cap0 then
			cap0, capture = toParse:match( '^([x\195]\151? ?10<sup>(%-?%d+)</sup>%s*)' )
		end
		if cap0 then
			result.e = capture
			toParse = toParse:sub( cap0:len() + 1 )
		end
		if result[1] == '10' and not result.e and not result.fraction then
			cap0, capture = toParse:match( '^(<sup>(%-?%d+)</sup>%s*)' )
			if cap0 then
				result[1] = false
				result.e = capture
				toParse = toParse:sub( cap0:len() + 1 )
			end
		end

		if toParse == '' then
			return result
		end
		
		-- unités
		local texteUnit = toParse
		toParse = toParse:gsub( '^([^%[<]-)<sup>(%d)</sup>', '%1%2' )
		if Data.unit[ toParse ]
			or toParse:match( '%b<>' )
			or  toParse:match( 'UNIQ%-%-%a+%-%x%x%x%x%x%x%x%x%-QINU' ) 
			or mw.ustring.match( toParse, '^%a+$' ) 
		then
			result[ #result + 1] = toParse
			toParse = ''
		elseif toParse:match( '%b<>' ) then
			toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
		end
		if toParse ~= '' then
			local unit, exp
			toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
			repeat
				-- unité contenant un lien
				cap0, unit, exp = mw.ustring.match( toParse, '^((/? ?[^%s%d/%[%]]*%b[][^%s%d/]*) ?(%-?%d*)%s*)' )
				if not cap0 then
					-- unité ne contenant pas de lien
					cap0, unit, exp = mw.ustring.match( toParse, '^((/? ?[^%s%d/]+) ?(%-?%d*)%s*)' )
				end
				if not cap0 then
					-- l/100 km
					cap0, unit, exp = mw.ustring.match( toParse, '^((/100 ?[^%s%d/]+) ?(%-?%d*)%s*)' )
				end
				if cap0 then
					if unit:match( '%-$' ) and exp ~= '' then -- rustine pour quand le "-" se retrouve dans la capture "unit" au lieu de la capture "exp"
						unit = unit:gsub( '%-$', '' )
						exp = '-' .. exp
					elseif exp == '-' then -- rustine pour quand un "-" a été capturé dans "exp" mais sans qu'il y ait de chiffres après
						unit = cap0
						exp = ''
					end
					if Data.unit[ unit ] or mw.ustring.match( unit, '[%a€£$¥«»]' ) then
						result[ #result + 1] = unit
						result[ #result + 1] = exp
						toParse = toParse:sub( cap0:len() + 1 )
					else
						break
					end
				end
			until toParse == '' or not cap0
		end

		if toParse == '' then
			if #result > 3 then
				local estSimpleTexte = true
				for r = 2, #result, 2 do
					if Data.unit[ result[ r ] ] 
						or result[ r ]:sub( 1, 1 ) == '/'
						or Data.prefix[ result[ r ]:sub( 1, 1 ) ] and Data.unit[ result[ r ]:sub( 2 ) ]
						or Data.prefix[ result[ r ]:sub( 1, 2 ) ] and Data.unit[ result[ r ]:sub( 3 ) ]
						or result[ r + 1 ] and result[ r + 1 ] ~= ''
					then
						estSimpleTexte = false
						break
					end
				end
				if estSimpleTexte then
					result[ 2 ] = texteUnit
					for r = #result, 3, -1 do
						result[ r ] = nil
					end
				end
			end
			if #result > 1 and result[ #result ] == '' then
				result[ #result ] = nil
			end
			return result
		else
			-- une partie de la chaine n'a pas pu être décodée, on retourne la chaine originale
			addErrorCat = true
			return { texte }
		end
	else
		return { }
	end
end

---
-- nomUnit retourne le nom français du code d'une unité et de son exposant.
-- si le code de l'unité n'est pas reconnu, retourne false.
function p.nomUnit( unit, exposant )
	unit = trim( unit )
	if not dataSuccess or type( unit ) ~= 'string' then
		return false
	end
	-- nettoyage des liens et balise HTML
	unit = unit:gsub( '^/' , '' )
	if unit:match( '%[' ) then
		local Delink = require( 'Module:Delink' )
		unit = Delink._delink{ unit }
	end
	if unit:match( '<' ) then
		unit = unit:gsub( '%b<>', '' )
	end
	
	-- /100
	local divisor = ''
	if unit:sub( 1, 2 ) == '10' then
		divisor, unit = unit:match( '^(1[0 ]*)(.+)$' )
		local divisorName = { 
			['10'] = 'dix ',
			['100'] = 'cent ',
			['1000'] = 'mille ',
			['10000'] = 'dix-mille ',
			['100000'] = 'cent-mille ',
			['1000000'] = 'un million de ',
			['1000000000'] = 'un millard de ',
		}
		divisor = divisorName[ divisor:gsub( ' ', '' ) ]
	end

	-- récupère le nom de l'unité
	local unitTab = Data.unit[ unit ]
	local unitPrefix = { nom = '' }
	if not unitTab then
		unitTab = Data.unit[ unit:sub( 2 ) ]
		unitPrefix = Data.prefix[ unit:sub( 1, 1 ) ]
		if not ( unitTab and unitPrefix ) then
			-- pour µ, Ki, Mi, Gi... qui sont codé sur deux octets
			unitTab = Data.unit[ unit:sub( 3 ) ]
			unitPrefix = Data.prefix[ unit:sub( 1, 2 ) ]
			if not ( unitTab and unitPrefix ) then
				unitTab = false
			end
		end
	end

	-- récupère le nom de l'exposant
	if trim( exposant ) then
		local exp = tonumber( exposant )
		exp = exp and Data.exposant[ math.abs( exp ) ]
		exposant = exp or ' puissance ' .. exposant
	else
		exposant = ''
	end

	-- assemble les deux partie
	if type( unitTab ) == 'table' and type( unitTab.nom ) == 'string' then
		return divisor .. unitPrefix.nom .. unitTab.nom .. exposant
	elseif unit:match( '[/%d]' ) then
		-- ce n'est pas du texte simple, on anule l'infobule
		return false
	else
		return unit .. exposant
	end
end

function p._unite( args )
	-- formatage du nombre
	local nombre = p.formatNombres( args[1], args.arrondi, args['décimales'] )
	if nombre == '' then
		nombre = nil
	end

	local wiki = {}

	-- prefix est un paramètre interne défini par p.parseUnit, utile notamment lorsque {{unité}} est utilisé dans les infobox
	if args.prefix then
		wiki[ #wiki + 1 ] = args.prefix
	end

	if nombre then
		wiki[ #wiki + 1 ] = nombre
	end

	-- fraction
	local fraction = args.fraction
	if fraction then
		fraction = fractionUnicode[ fraction ] or fraction
		local nom, den = fraction:match( '^(.-)/(.+)$' )
		if nom then
			if nom:match( '^[%dn]%d?$' ) and den:match( '^[%daeoxhklmnpst]$' ) then
				nom = nom:gsub( '[%dn()=+-]', supUnicode )
				den = den:gsub( '[%daeoxhklmnpst()=+-]', subUnicode )
			else
				nom = '<sup style="font-size: 70%; vertical-align: 0.4em;">'
					.. p.formatNombres( nom )
					.. '</sup>'
				den = '<sub style="font-size: 70%; vertical-align: 0em;">'
					.. p.formatNombres( den )
					.. '</sub>'
			end
			fraction = nom .. '⁄' .. den
		end

		if nombre then
			wiki[ #wiki + 1 ] = nbsp
		end
		wiki[ #wiki + 1 ] = fraction
	end

	-- à, et, ou, ×, – (tiret cadratin)
	local specificArgs = { '–', 'à', 'et', 'ou', '/', '//', '×', '××', '±' }
	for i = 1, #specificArgs do
		local name = specificArgs[ i ]
		local v = args[ name ] and trim( args[ name ] )
		if v then
			v = p.formatNombres( v )
			if name == '//' then 
				name = '/'
			elseif name == '××' then
				name = '×'
			end
			if name == '–' and nombre and nombre:match( '^[^−]' ) and v:match( '^[^−]' ) then
				-- pas d'espace pour le tiret cadratin entre deux nombres positifs
				wiki[ #wiki + 1 ] = '–'
			elseif name == '×' or name == '±' then
				wiki[ #wiki + 1 ] = nbsp .. name .. nbsp
			else
				wiki[ #wiki + 1 ] = nbsp .. name .. ' '
			end
			wiki[ #wiki + 1 ] = v
		end
	end

	-- analyse de l'unité pour la conversion (mais ne sera affiché qu'après l'incertitude + et - séparé)
	local i = 1
	local unit = trim( args[ 2 * i ] )
	local units = ''
	local nomUnits, par = {}, false
	while unit do
		local exp = p.parseNombre( args[ 2 * i + 1 ] )
		local sep = ''
		-- gestion des exposants
		local expUnit = ''
		if exp == '' then
			local suffix = unit:sub( -2 ) -- yes, it's 2 bytes
			if suffix == '²' then
				exp = '2'
				unit = unit:sub( 1, -3 )
			elseif suffix == '³' then
				exp = '3'
				unit = unit:sub( 1, -3 )
			end
		end
		if #exp > 0 then
			expUnit = '<sup>' .. exp:gsub( '^-', '−') .. '</sup>'  -- remplace le tiret par un vrai signe moins
		end
		-- gestion de la séparation des unités et des unités en dénominateur
		if unit:sub( 1, 1 ) == '/' then
			sep = '/'
			unit = trim( unit:sub( 2 ) ) or ''
			if not par then
				par = true
				if unit:sub( 1, 2 ) == '10' then
					nomUnits[ #nomUnits + 1 ] = 'pour'
				else
					nomUnits[ #nomUnits + 1 ] = 'par'
				end
			else
				nomUnits[ #nomUnits + 1 ] = 'et par'
				if nomUnits[ #nomUnits - 2 ] == 'et par' then
					nomUnits[ #nomUnits - 2 ] = 'par'
				end
			end
		elseif units ~= '' then
			sep = nbsp
		end
		if exp:match( '^-' ) and not par then
			par = true
			nomUnits[ #nomUnits + 1 ] = 'par'
		end
		-- remplacement de l'unité par son symbole
		if Data.unit[ unit ] then
			-- unit = Data.unit[ unit ].symbole
			-- désactivé car ne gère pas les multiple tel mL
		end
		units = units .. sep .. unit .. expUnit
		local nomUnit = p.nomUnit( unit, exp )
		if nomUnit then
			nomUnits[ #nomUnits + 1 ] = nomUnit
		else
			-- si le code de l'unité n'est pas reconnu, insère false en première position de la table.
			table.insert( nomUnits, 1, false )
		end
		i = i + 1
		unit = trim( args[ 2 * i ] )
	end

	local unitFullName = nomUnits[1] and table.concat( nomUnits, ' ' ) or false

	-- conversion
	if unitFullName then
		local nameSingular = mw.ustring.gsub( unitFullName, '(%a)s%f[%A]', '%1' )
		local multiple = 1
		local convertTable = Data.convert[ nameSingular ]
		if not convertTable and #nameSingular > 5 then
			-- gesion des multiples (Kilo, méga, mili...)
			local prefix = Data.prefix[ nameSingular:sub( 1, 4 ) ] or Data.prefix[ nameSingular:sub( 1, 5 ) ]
			local _, par = nameSingular:find( ' par ' )
			local prefix2
			if par then
				prefix2 = Data.prefix[ nameSingular:sub( par + 1, 4 ) ] or Data.prefix[ nameSingular:sub( par + 1, 5 ) ]
			end
			if prefix and Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ) ] then
				convertTable = Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ) ]
				multiple = 10 ^ prefix.puissance
			elseif prefix2 and  Data.convert[ nameSingular:gsub( ' par ' .. prefix2.nom, '' ) ] then
				convertTable = Data.convert[ nameSingular:gsub( ' par ' .. prefix2.nom, '' ) ]
				multiple = 1 / 10 ^ prefix2.puissance
			elseif prefix and prefix2 and Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ] then
				convertTable = Data.convert[ nameSingular:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ]
				multiple = 10 ^ prefix.puissance / 10 ^ prefix2.puissance
			end
		end
		if convertTable then
			if type( convertTable[1] ) ~= 'table' then
				convertTable = { convertTable }
			end
			for i = 1, #wiki do
				local v = wiki[ i ]
				local n = tonumber( p.parseNombre( v ) )
				if n then
					n = n * 10 ^ ( tonumber( p.parseNombre( args.e ) ) or 0 )
					local converted = {}
					for _, c in ipairs( convertTable ) do
						local nConverted = n
						if c.inverse then
							nConverted = 1 / n
						end
						if c.M then
							-- M = masse molaire
							local M = tonumber( args.M )
							if not M then
								break
							end
							if c.M == '*' then
								nConverted = nConverted * M
							elseif c.M == '/' then
								nConverted = nConverted / M
							end
						end
						nConverted = nConverted * multiple * c[2] + ( c[3] or 0 )
						-- format
						nConverted = p.formatNum{ nConverted, round = c.round or 6, noHtml = true }
						local sep = ' '
						if c[1]:sub( 1, 2 ) == '°' then -- yes, it's 2 bytes
							sep = ''
						end
						converted[ #converted + 1 ] = nConverted .. sep.. c[1]
					end
					wiki[ i ] = '<span title="' .. table.concat( converted, ' ou ' ) ..'">' .. v ..'</span>'
				end
			end
		end
	end

	-- incertitude avec + et − séparés
	if trim( args['+'] ) then
		local approximation = '+' .. p.formatNombre( args['+'] ) .. ''
		if trim( args['−'] ) then
			approximation = approximation .. '<br> −' .. p.formatNombre( args['−'] )
		end
		wiki[ #wiki + 1 ] = '<span class="nowrap"><span style="display:inline-block; padding-left:0.2em; vertical-align:top; line-height:1em; font-size:80%; text-align:left;">'
		wiki[ #wiki + 1 ] = approximation .. '</span></span>'
	end

	-- puissance de 10
	local exposant = trim( args.e )
	if exposant then
		exposant = p.formatNombre( exposant )
		if nombre then
			if trim( args['±'] ) and not nombre:match( '^%(' ) then
				table.insert( wiki, 1, '(' )
				wiki[ #wiki + 1 ] = ')'
			end
			wiki[ #wiki + 1 ] = nbsp .. '×' .. nnbsp .. '10<sup>' .. exposant .. '</sup>'
		else
			wiki[ #wiki + 1 ] = '10<sup>' .. exposant .. '</sup>'
		end
	end

	if units ~= '' then
		local sep = nbsp
		if not ( nombre or args.fraction or exposant ) then
			sep = ''
		else
			local symbole = Data.unit[ units ] and Data.unit[ units ].symbole
			if symbole == '°' or symbole == '′' or symbole == '″' then
				sep = ''
			end
		end
		-- ajoute une abréviation si le nom de l'unité est différent de l'unité (en considérant les espaces qui peuvent être devenus insécables)
		if unitFullName and unitFullName ~= units:gsub( nbsp, ' ' ) then
			units = string.format( '<abbr class="abbr" title="%s">%s</abbr>', unitFullName, units )
		end
		wiki[ #wiki + 1 ] = sep .. units
	end

	if #wiki > 0 then
		return table.concat( wiki )
	end
end

function p.unite( frame )
	local args
	if type( frame ) == 'table' then
		if type( frame.getParent ) == 'function' then
			args = frame:getParent().args
		else
			args = frame
		end
	end
	if args then
		addErrorCat = false
		args[1] = trim( args[1] ) or false
		local basique = require( 'Module:Yesno' )( args.basique )
		if args[1] and not basique then
			if args[1]:match('[^%d,. -]') then
				local tempArgs = p.parseUnit( args[1] )
				if not ( args[2] and tempArgs[2] ) then
					for k, v in pairs( tempArgs ) do
						args[k] = v
					end
				end
			end
			args[2] = trim( args[2] ) or false
			if args[2] and not args[3] then
				-- cas ou le paramètre 2 contient 'm3' ou 'km2'
				local a, d = args[2]:match('^(%a%a?)(%d)$')
				if a and Data.unit[a] then
					args[2] = a
					args[3] = d
				end
				-- cas ou le paramètre 2 contient 'km/s' ou 'm3/h'
				if args[2]:match('/') then
					local tempArgs = p.parseUnit( args[2] )
					args[2] = false
					if tempArgs[1] ~= false then
						table.insert( tempArgs, 1, false )
					end
					for k, v in pairs( tempArgs ) do
						if args[k] and v then
							addErrorCat = true
						end
						args[k] = args[k] or v
					end
				end
			end
		end
		-- args alias
		args['×'] = args['×'] or args['x']  -- lettre x → signe multiplié
		args['±'] = args['±'] or args['+-'] or args['+/-']
		if args['+'] then
			args['−'] = args['−'] or args['-'] -- tiret → signe moins
		else
			args['–'] = args['–'] or args['-'] -- tiret → demi-cadratin
		end
		local cat = ''
		if addErrorCat and mw.title.getCurrentTitle():inNamespaces( 0, 4, 8, 10, 12, 14, 100, 828 ) then
			cat = errorCat
			mw.log( errorCat ,' → ', args[1], '|', args[2] )
		end
		return ( p._unite( args ) or '' ) .. cat
	end
end

function p.emulationFormatnum( frame )
	local args = frame:getParent().args
	return p.formatNombres( args[1] )
end

return p