Actions

Module

« Unité » : différence entre les versions

De Wikimanche

(Mise à jour module)
(Mise à jour)
 
Ligne 18 : Ligne 18 :
['+'] = '⁺', ['-'] = '⁻', ['='] = '⁼', ['('] = '⁽', [')'] = '⁾', ['n'] = 'ⁿ' }
['+'] = '⁺', ['-'] = '⁻', ['='] = '⁼', ['('] = '⁽', [')'] = '⁾', ['n'] = 'ⁿ' }
local subUnicode = { ['0'] = '₀', ['1'] = '₁', ['2'] = '₂', ['3'] = '₃', ['4'] = '₄', ['5'] = '₅', ['6'] = '₆', ['7'] = '₇', ['8'] = '₈', ['9'] = '₉',
local subUnicode = { ['0'] = '₀', ['1'] = '₁', ['2'] = '₂', ['3'] = '₃', ['4'] = '₄', ['5'] = '₅', ['6'] = '₆', ['7'] = '₇', ['8'] = '₈', ['9'] = '₉',
['+'] = '₊', ['-'] = '₋', ['='] = '₌', ['('] = '₍', [')'] = '₎',
['a'] = 'ₐ', ['e'] = 'ₑ', ['o'] = 'ₒ', ['x'] = 'ₓ', ['h'] = 'ₕ', ['k'] = 'ₖ', ['l'] = 'ₗ',
['a'] = 'ₐ', ['e'] = 'ₑ', ['o'] = 'ₒ', ['x'] = 'ₓ', ['h'] = 'ₕ', ['k'] = 'ₖ', ['l'] = 'ₗ',
['m'] = 'ₘ', ['n'] = 'ₙ', ['p'] = 'ₚ', ['s'] = 'ₛ', ['t'] = 'ₜ',
['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*(%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 34 : 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
Ligne 42 : Ligne 72 :
end
end
local result = nombre
local result = nombre
-- remplacement des signes moins ou demi-cadratin par un tiret
-- remplacement des signes moins 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( '\226\128[\128-\138\175]', ' ' ) -- U+2002 à U+200A et U+202F
:gsub( ' ', ' ' )
:gsub( nnbsp, ' ' )
:gsub( ' ', ' ' )
:gsub( ' ', ' ' )
:gsub( '\226\128[\132-\138]', ' ' ) -- U+2004 à U+200A
:gsub( ' ', ' ' )
-- trim
-- trim
:gsub( '^%s*(%S?.-)%s*$', '%1' )
:gsub( '^%s*(%S?.-)%s*$', '%1' )
Ligne 72 : 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
Ligne 94 : Ligne 136 :
end
end
end
end
 
return result
return result
end
end
Ligne 127 : Ligne 169 :
exponent = '<sup>' .. exponent:gsub('^%+?(%-?)0?', { ['-'] = '−', [''] = '' } ) .. '</sup>'
exponent = '<sup>' .. exponent:gsub('^%+?(%-?)0?', { ['-'] = '−', [''] = '' } ) .. '</sup>'
end
end
exponent = ' ×10' .. exponent
if num == '1' then
return '10' .. exponent
end
exponent = nbsp .. '×' .. nnbsp .. '10' .. exponent
else
else
exponent = ''
exponent = ''
Ligne 140 : Ligne 185 :
end
end


local moins, entier, fraction = num:match( '^(%-?)(%d*)%.?(%d*)$' )
local moins, entier, deci = num:match( '^(%-?)(%d*)%.?(%d*)$' )
if not entier then
if not entier then
return num
return num
Ligne 153 : Ligne 198 :
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 ~= '' or ( decimals and decimals > 0 ) then
if deci ~= '' or ( decimals and decimals > 0 ) then
if decimals and decimals > #fraction then
if decimals and decimals > #deci then
fraction = fraction .. string.rep( '0', decimals - #fraction )
deci = deci .. string.rep( '0', decimals - #deci )
end
end
if #fraction > 4 then
if #deci > 3 then
fraction = ',' .. fraction:gsub( '(%d%d%d)', '%1\194\160' ):gsub( '\194\160$', '' )
deci = ',' .. deci:gsub( '(%d%d%d)', '%1' .. nbsp ):gsub( nbsp .. '$', '' )
else
else
fraction = ',' .. fraction
deci = ',' .. deci
end
end
end
end


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


Ligne 179 : Ligne 224 :
function p.formatNombres( nombres, round, decimals )
function p.formatNombres( nombres, round, decimals )
if type( nombres ) == 'number' then
if type( nombres ) == 'number' then
return p.formatNum( nombres, round, decimals )
return p.formatNum{ nombres, round = round, decimals = decimals }
elseif type( nombres ) == 'string' then
elseif type( nombres ) == 'string' then
-- retire les chiffres des strip marker
-- retire les chiffres des strip markers
local strip, i = {}, 0
nombres = escapeStripMarkers( nombres )
nombres = nombres:gsub(
 
'UNIQ%-%-%a+%-%x%x%x%x%x%x%x%x%-QINU',
-- formatage proprement dit
function ( marker )
i = i + 1
strip[ tostring( i ) ] = marker
return 'UNIQ^' .. i .. '¤QINU'
end
)
-- formatage proprement dit
nombres = p.sanitizeNum( nombres )
nombres = p.sanitizeNum( nombres )
local formatN = function ( n )
local formatN = function ( n )
return p.formatNombre( n, round, decimals )
return p.formatNombre( n, round, decimals )
end
end
nombres = nombres
if nombres:match('%d%-%d') then
:gsub( '%-?%f[%d.,][%d., ]*%de[+-]?%d+', formatN )
nombres = nombres:gsub( '%f[%d.,][%d., ]*%d', formatN )
: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 )


-- réintroduction des strip marker
nombres = nombres:gsub( 'UNIQ^(%d+)¤QINU', strip )
return nombres
return nombres
else
else
Ligne 216 : Ligne 259 :
et = 'et',
et = 'et',
ou = 'ou',
ou = 'ou',
['/'] = '/',
['/'] = '/', [';'] = '/',
['–'] = '–', ['-'] = '–', -- demi cadratin et tiret
['//'] = '//',
['–'] = '–', ['—'] = '–', ['-'] = '–', -- demi cadratin, cadratin et tiret
['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
['+'] = '+',
['+'] = '+',
Ligne 226 : Ligne 270 :


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


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


-- fraction
-- fraction
match, capture = toParse:match( '^((%d*/%d+)%s*)' )
capture = mw.ustring.sub( toParse, 1, 1 )
if not match then
if fractionUnicode[ capture ] then
match, capture = toParse:match( '^((\194[\188-\190])%s*)' ) -- ¼ à ¾
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
end
if not match then
if cap0 then
match, capture = toParse:match( '^((\226\133[\144-\158])%s*)' ) -- ⅐ à ⅞
if result[1] and capture:match( '^/' ) then
end
if match then
if capture:match( '^/' ) then
local n = result[1]:match( ' %d+$' ) or result[1]:match( '^%d+$' )  or ''
local n = result[1]:match( ' %d+$' ) or result[1]:match( '^%d+$' )  or ''
result[1] = result[1]:sub( 1, -1 - #n )
result[1] = result[1]:sub( 1, -1 - #n )
Ligne 274 : Ligne 338 :
result.fraction = capture
result.fraction = capture
end
end
toParse = toParse:sub( match:len() + 1 )
toParse = toParse:sub( cap0:len() + 1 )
end
end
end


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


-- 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
end


if toParse == '' then
return result
end
-- unités
-- unités
if Data.unit[ toParse ] or mw.ustring.match( toParse, '^%a+$' ) or toParse:match( '%b<>' ) then
local texteUnit = toParse
table.insert( result, 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 = ''
toParse = ''
elseif toParse ~= '' then
elseif toParse:match( '%b<>' ) then
toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
end
if toParse ~= '' then
local unit, exp
local unit, exp
toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
repeat
repeat
-- unité contenant un lien
-- unité contenant un lien
match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/%[%]]*%b[][^%s%d/]*) ?(%-?%d*)%s*)' )
cap0, unit, exp = mw.ustring.match( toParse, '^((/? ?[^%s%d/%[%]]*%b[][^%s%d/]*) ?(%-?%d*)%s*)' )
if not match then
if not cap0 then
-- unité ne contenant pas de lien
-- unité ne contenant pas de lien
match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/]+) ?(%-?%d*)%s*)' )
cap0, unit, exp = mw.ustring.match( toParse, '^((/? ?[^%s%d/]+) ?(%-?%d*)%s*)' )
end
end
if not match then
if not cap0 then
-- l/100 km
-- l/100 km
match, unit, exp = mw.ustring.match( toParse, '^((/100 ?[^%s%d/]+) ?(%-?%d*)%s*)' )
cap0, unit, exp = mw.ustring.match( toParse, '^((/100 ?[^%s%d/]+) ?(%-?%d*)%s*)' )
end
end
if match then
if cap0 then
if unit:match( '%-$' ) and exp ~= '' 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( '%-$', '' )
unit = unit:gsub( '%-$', '' )
exp = '-' .. exp
exp = '-' .. exp
elseif exp == '-' then
elseif exp == '-' then -- rustine pour quand un "-" a été capturé dans "exp" mais sans qu'il y ait de chiffres après
unit = match
unit = cap0
exp = ''
exp = ''
end
end
if Data.unit[ unit ] or mw.ustring.match( unit, '[%a€£$«»]' ) then
if Data.unit[ unit ] or mw.ustring.match( unit, '[%a€£$¥«»]' ) then
table.insert( result, unit )
result[ #result + 1] = unit
table.insert( result, exp )
result[ #result + 1] = exp
toParse = toParse:sub( match:len() + 1 )
toParse = toParse:sub( cap0:len() + 1 )
else
else
break
break
end
end
end
end
until toParse == '' or not match
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
if #result > 1 and result[ #result ] == '' then
result[ #result ] = nil
result[ #result ] = nil
Ligne 360 : 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, exposant )
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
-- nettoyage des liens et balise HTML
unit = unit:gsub( '^/' , '' )
unit = unit:gsub( '^/' , '' )
Ligne 375 : Ligne 497 :
if unit:match( '<' ) then
if unit:match( '<' ) then
unit = unit:gsub( '%b<>', '' )
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
end


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


function p._unite( args )
function p._unite( args )
-- remplacement de certains caractères, pour simplifier les pattern
-- formatage du nombre
local nombre = p.sanitizeNum( args[1] )
local nombre = p.formatNombres( args[1], args.arrondi, args['décimales'] )
if nombre == '' then
if nombre == '' then
nombre = nil
nombre = nil
else
-- formatage du nombre
nombre = p.formatNombres( nombre, args.arrondi, args['décimales'] )
end
end


local wiki = { args.prefix or '', nombre }   -- prefix est un paramètre interne défini par p.parseUnit, utile notamment lorsque {{unité}} est utilisé dans les infobox
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
-- fraction
if args.fraction then
local fraction = args.fraction
local nom, den = args.fraction:match( '^(.-)/(.+)$' )
if fraction then
fraction = fractionUnicode[ fraction ] or fraction
local nom, den = fraction:match( '^(.-)/(.+)$' )
if nom then
if nom then
if nom:match( '^[ %dn()=+-]+$' ) and den:match( '^[ %daeoxhklmnpst()=+-]$' ) then
if nom:match( '^[%dn]%d?$' ) and den:match( '^[%daeoxhklmnpst]$' ) then
nom = nom:gsub( '[%dn()=+-]', supUnicode )
nom = nom:gsub( '[%dn()=+-]', supUnicode )
den = den:gsub( '[%daeoxhklmnpst()=+-]', subUnicode )
den = den:gsub( '[%daeoxhklmnpst()=+-]', subUnicode )
else
else
nom = '<sup style="font-size: 70%; vertical-align: 0.4em;">' .. nom .. '</sup>'
nom = '<sup style="font-size: 70%; vertical-align: 0.4em;">'
den = '<sub style="font-size: 70%; vertical-align: 0em;">' .. den .. '</sub>'
.. p.formatNombres( nom )
.. '</sup>'
den = '<sub style="font-size: 70%; vertical-align: 0em;">'
.. p.formatNombres( den )
.. '</sub>'
end
end
args.fraction = nom .. '⁄' .. den
fraction = nom .. '⁄' .. den
end
end


if nombre then
if nombre then
table.insert( wiki, '\194\160' )
wiki[ #wiki + 1 ] = nbsp
end
end
table.insert( wiki, args.fraction )
wiki[ #wiki + 1 ] = fraction
end
end


-- à, et, ou, ×, – (tiret cadratin)
-- à, et, ou, ×, – (tiret cadratin)
local specificArgs = { '–', 'à', 'et', 'ou', '/', '×', '××', '±' }
local specificArgs = { '–', 'à', 'et', 'ou', '/', '//', '×', '××', '±' }
for _, name in ipairs( specificArgs ) do
for i = 1, #specificArgs do
local v = trim( args[ name ] )
local name = specificArgs[ i ]
local v = args[ name ] and trim( args[ name ] )
if v then
if v then
v = p.formatNombres( v )
v = p.formatNombres( v )
if name == '//' then
name = '/'
elseif name == '××' then
name = '×'
end
if name == '–' and nombre and nombre:match( '^[^−]' ) and v:match( '^[^−]' ) then
if name == '–' and nombre and nombre:match( '^[^−]' ) and v:match( '^[^−]' ) then
-- pas d'espace pour le tiret cadratin entre deux nombres positifs
-- pas d'espace pour le tiret cadratin entre deux nombres positifs
table.insert( wiki, '–' )
wiki[ #wiki + 1 ] = ''
elseif name == '/' then
-- espace demi-cadratin (U+2002 ou &ensp;) avec /
table.insert( wiki, ' / ' )
elseif name == '××' then
table.insert( wiki, '\194\160×\194\160' )
elseif name == '×' or name == '±' then
elseif name == '×' or name == '±' then
table.insert( wiki, '\194\160' .. name .. '\194\160' )
wiki[ #wiki + 1 ] = nbsp .. name .. nbsp
else
else
table.insert( wiki, ' ' .. name .. ' ' )
wiki[ #wiki + 1 ] = nbsp .. name .. ' '
end
end
table.insert( wiki, v )
wiki[ #wiki + 1 ] = v
end
end
end
end


-- analyse de l'unité pour la conversion (mais ne sera affiché qu'après l'incertitude + et - séparé
-- 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 479 : Ligne 630 :
local expUnit = ''
local expUnit = ''
if exp == '' then
if exp == '' then
if unit:sub( -2 ) == '²' then
local suffix = unit:sub( -2 ) -- yes, it's 2 bytes
if suffix == '²' then
exp = '2'
exp = '2'
unit = unit:sub( 1, -3 )
unit = unit:sub( 1, -3 )
elseif unit:sub( -2 ) == '³' then
elseif suffix == '³' then
exp = '3'
exp = '3'
unit = unit:sub( 1, -3 )
unit = unit:sub( 1, -3 )
Ligne 491 : Ligne 643 :
end
end
-- gestion de la séparation des unités et des unités en dénominateur
-- gestion de la séparation des unités et des unités en dénominateur
if units ~= '' then
if unit:sub( 1, 1 ) == '/' then
if unit:sub( 1, 1 ) == '/' then
sep = '/'
sep = '/'
unit = trim( unit:sub( 2 ) ) or ''
unit = unit:sub( 2 )
if not par then
if not par then
par = true
par = true
if unit:sub( 1, 2 ) == '10' then
table.insert( nomUnits, 'par' )
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 nomUnits[ #nomUnits - 2 ] == 'et par' then
nomUnits[ #nomUnits - 2 ] = 'par'
end
end
end
elseif units ~= '' then
sep = nbsp
end
end
if exp:match( '^-' ) and not par then
if exp:match( '^-' ) and not par then
par = true
par = true
table.insert( nomUnits, 'par' )
nomUnits[ #nomUnits + 1 ] = 'par'
end
end
-- remplacement de l'unité par son symbole
-- remplacement de l'unité par son symbole
Ligne 513 : Ligne 672 :
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
local unitFullName = nomUnits[1] and table.concat( nomUnits, ' ' ) or false


-- conversion
-- conversion
local unitNameString = nomUnits[1] and table.concat( nomUnits, ' ' ) or ''
if unitFullName then
unitNameString = mw.ustring.gsub( unitNameString, '(%a)s%f[%A]', '%1' )
local nameSingular = mw.ustring.gsub( unitFullName, '(%a)s%f[%A]', '%1' )
local multiple = 1
local multiple = 1
local convertTable = Data.convert[ unitNameString ]
local convertTable = Data.convert[ nameSingular ]
if not convertTable and #unitNameString > 5 then
if not convertTable and #nameSingular > 5 then
-- gesion des multiples (Kilo, méga, mili...)
-- gesion des multiples (Kilo, méga, mili...)
local prefix = Data.prefix[ unitNameString:sub( 1, 4 ) ] or Data.prefix[ unitNameString:sub( 1, 5 ) ]
local prefix = Data.prefix[ nameSingular:sub( 1, 4 ) ] or Data.prefix[ nameSingular:sub( 1, 5 ) ]
local _, par = unitNameString:find( ' par ' )
local _, par = nameSingular:find( ' par ' )
local prefix2
local prefix2
if par then
if par then
prefix2 = Data.prefix[ unitNameString:sub( par + 1, 4 ) ] or Data.prefix[ unitNameString:sub( par + 1, 5 ) ]
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
end
if prefix and Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ) ] then
if convertTable then
convertTable = Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ) ]
if type( convertTable[1] ) ~= 'table' then
multiple = 10 ^ prefix.puissance
convertTable = { convertTable }
elseif prefix2 and  Data.convert[ unitNameString:gsub( ' par ' .. prefix2.nom, '' ) ] then
end
convertTable = Data.convert[ unitNameString:gsub( ' par ' .. prefix2.nom, '' ) ]
for i = 1, #wiki do
multiple = 1 / 10 ^ prefix2.puissance
local v = wiki[ i ]
elseif prefix and prefix2 and Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ] then
local n = tonumber( p.parseNombre( v ) )
convertTable = Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ]
if n then
multiple = 10 ^ prefix.puissance / 10 ^ prefix2.puissance
n = n * 10 ^ ( tonumber( p.parseNombre( args.e ) ) or 0 )
end
local converted = {}
end
for _, c in ipairs( convertTable ) do
if convertTable then
local nConverted = n
if type( convertTable[1] ) ~= 'table' then
if c.inverse then
convertTable = { convertTable }
nConverted = 1 / n
end
end
for i, v in ipairs( wiki ) do
if c.M then
local n = tonumber( p.parseNombre( v ) )
-- M = masse molaire
if n then
local M = tonumber( args.M )
n = n * 10 ^ ( tonumber( p.parseNombre( args.e ) ) or 0 )
if not M then
local converted = {}
break
for _, c in ipairs( convertTable ) do
end
local nConverted = n
if c.M == '*' then
if c.inverse then
nConverted = nConverted * M
nConverted = 1 / n
elseif c.M == '/' then
end
nConverted = nConverted / M
if c.M then
end
-- M = masse molaire
local M = tonumber( args.M )
if not M then
break
end
end
if c.M == '*' then
nConverted = nConverted * multiple * c[2] + ( c[3] or 0 )
nConverted = nConverted * M
-- format
elseif c.M == '/' then
nConverted = p.formatNum{ nConverted, round = c.round or 6, noHtml = true }
nConverted = nConverted / M
local sep = ' '
if c[1]:sub( 1, 2 ) == '°' then -- yes, it's 2 bytes
sep = ''
end
end
converted[ #converted + 1 ] = nConverted .. sep.. c[1]
end
end
nConverted = nConverted * multiple * c[2] + ( c[3] or 0 )
wiki[ i ] = '<span title="' .. table.concat( converted, ' ou ' ) ..'">' .. v ..'</span>'
-- format
nConverted = p.formatNum{ nConverted, round = c.round or 6, noHtml = true }
local sep = ' '
if mw.ustring.sub( c[1], 1, 1 ) == '°' then
sep = ''
end
mw.log( c[1], mw.ustring.codepoint( sep, 1, 1))
table.insert( converted, nConverted .. sep.. c[1] )
end
end
wiki[ i ] = '<span title="' .. table.concat( converted, ' ou ' ) ..'">' .. v ..'</span>'
end
end
end
end
Ligne 589 : Ligne 757 :
approximation = approximation .. '<br> −' .. p.formatNombre( args['−'] )
approximation = approximation .. '<br> −' .. p.formatNombre( args['−'] )
end
end
table.insert( wiki, '<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 ] = '<span class="nowrap"><span style="display:inline-block; padding-left:0.2em; vertical-align:top; line-height:1em; font-size:80%; text-align:left;">'
table.insert( wiki, approximation .. '</span></span>' )
wiki[ #wiki + 1 ] = approximation .. '</span></span>'
end
end


Ligne 600 : Ligne 768 :
if trim( args['±'] ) and not nombre:match( '^%(' ) then
if trim( args['±'] ) and not nombre:match( '^%(' ) then
table.insert( wiki, 1, '(' )
table.insert( wiki, 1, '(' )
table.insert( wiki, ')' )
wiki[ #wiki + 1 ] = ')'
end
end
table.insert( wiki, '\194\160× 10<sup>' .. exposant .. '</sup>' )
wiki[ #wiki + 1 ] = nbsp .. '×' .. nnbsp .. '10<sup>' .. exposant .. '</sup>'
else
else
table.insert( wiki, '10<sup>' .. exposant .. '</sup>' )
wiki[ #wiki + 1 ] = '10<sup>' .. exposant .. '</sup>'
end
end
end
end


if units ~= '' then
if units ~= '' then
local sep = '\194\160'
local sep = nbsp
if not ( nombre or args.fraction or exposant ) then
if not ( nombre or args.fraction or exposant ) then
sep = ''
sep = ''
Ligne 618 : Ligne 786 :
end
end
end
end
-- ajoute une abréviation si le nom de l'unité est différent de l'unité (en retirant les espaces qui peuvent être devenus insécables)
-- 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 nomUnits[1] and table.concat( nomUnits ):gsub( ' ', '' ):gsub( '\194\160', '' ) ~= units:gsub( ' ', '' ):gsub( '\194\160', '' ) then
if unitFullName and unitFullName ~= units:gsub( nbsp, ' ' ) then
units = string.format( '<abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
units = string.format( '<abbr class="abbr" title="%s">%s</abbr>', unitFullName, units )
end
end
table.insert( wiki, sep .. units )
wiki[ #wiki + 1 ] = sep .. units
end
end


Ligne 634 : Ligne 802 :
if type( frame ) == 'table' then
if type( frame ) == 'table' then
if type( frame.getParent ) == 'function' then
if type( frame.getParent ) == 'function' then
args = frame:getParent().args;
args = frame:getParent().args
else
else
args = frame
args = frame
Ligne 640 : Ligne 808 :
end
end
if args then
if args then
addErrorCat = false
args[1] = trim( args[1] ) or false
args[1] = trim( args[1] ) or false
if args[1] then
local basique = require( 'Module:Yesno' )( args.basique )
if args[1] and not basique then
if args[1]:match('[^%d,. -]') then
if args[1]:match('[^%d,. -]') then
local tempArgs = p.parseUnit( args[1] )
local tempArgs = p.parseUnit( args[1] )
Ligne 651 : Ligne 821 :
end
end
args[2] = trim( args[2] ) or false
args[2] = trim( args[2] ) or false
if args[2] and not args[3] and args[2]:match('/') then
if args[2] and not args[3] then
local tempArgs = p.parseUnit( args[2] )
-- cas ou le paramètre 2 contient 'm3' ou 'km2'
args[2] = false
local a, d = args[2]:match('^(%a%a?)(%d)$')
if tempArgs[1] ~= false then
if a and Data.unit[a] then
table.insert( tempArgs, 1, false )
args[2] = a
args[3] = d
end
end
for k, v in pairs( tempArgs ) do
-- cas ou le paramètre 2 contient 'km/s' ou 'm3/h'
if args[k] and v then
if args[2]:match('/') then
addErrorCat = true
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
args[k] = args[k] or v
end
end
end
end
Ligne 674 : Ligne 853 :
end
end
local cat = ''
local cat = ''
if addErrorCat then
if addErrorCat and mw.title.getCurrentTitle():inNamespaces( 0, 4, 8, 10, 12, 14, 100, 828 ) then
cat = errorCat
cat = errorCat
mw.log( errorCat ,' → ', args[1], '|', args[2] )
end
end
return p._unite( args ) .. cat
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