Module:Unité : Différence entre versions

De Lagny-sur-Marne Wiki
Aller à : navigation, rechercher
(espaces insécables autour du ×)
(retouche de la modification précédente)
 
(32 révisions intermédiaires par le même utilisateur 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.
 +
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'] = 'ₜ',
 +
}
 
--- 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' )
+
texte = texte:gsub( '^%s*(%S?.-)%s*$', '%1' )
 
if texte ~= '' then
 
if texte ~= '' then
 
return texte
 
return texte
Ligne 10 : Ligne 31 :
 
elseif type( texte ) == 'number' then
 
elseif type( texte ) == 'number' then
 
return tostring( texte )
 
return tostring( texte )
 +
end
 +
end
 +
 +
function p.sanitizeNum( nombre )
 +
if type( nombre ) == 'number' then
 +
return tostring( nombre )
 +
elseif trim( nombre ) then
 +
local result = nombre
 +
-- trim
 +
:gsub( '^%s*(.*)%f[%s]%s*$', '%1' )
 +
-- remplacement des signes moins ou demi-cadratin par un tiret
 +
:gsub( '%−%f[%d]', '-')  -- U+2212
 +
: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
 +
:gsub( '\194\160', ' ' )
 +
result = mw.ustring.gsub( result, '[ -  ]', ' ' ) -- U+2002 à U+220A et U+202F
 +
return result
 +
else
 +
return ''
 
end
 
end
 
end
 
end
  
 
---
 
---
-- parseNum transforme si possible une chaine formater en un chaine interprétable par tonumber()
+
-- 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.
 
-- retourne une chaine pour éviter les arrondi éventuels de lua.
-- si nombre n'est pas un nombre ou une chaine retourne une chaine vide.
+
-- 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 )
 
function p.parseNombre( nombre )
 +
local result
 
if type( nombre ) == 'number' then
 
if type( nombre ) == 'number' then
nombre = tostring( nombre )
+
return tostring( nombre )
elseif type( nombre ) ~= 'string' then
+
else
return ''
+
-- remplacement des signes moins ou demi-cadratin par un tiret
 +
result = p.sanitizeNum( nombre )
 +
if result == '' then
 +
return ''
 +
elseif not result:match( '^%-?[%d., ]*%d$' ) then
 +
return nombre
 +
end
 
end
 
end
 
 
-- remplacement des signes moins ou demi-cadratin par un tiret
+
-- suppression espaces
nombre = nombre:gsub( '^&minus', '-' )
+
result = result:gsub( ' ', '' )
:gsub( '^\226\128[\146\147]', '-' ) -- U+2012, U+2013 (voir [[Tiret]])
 
:gsub( '^\226\136\147', '-' ) -- U+2212
 
 
 
if nombre:match( '^%-?[%d., \194\160]*%d$' ) then
+
-- gestion des points et des virgules
-- suppression espaces et espaces insécables
+
if result:match( '[.,]' ) then
nombre = nombre:gsub( ' ', '' ):gsub( '\194\160', '' )
+
if result:match( '%d%.%d%d%d%.%d' ) then
+
-- type 12.345.678
if nombre:match( '[.,]' ) then
+
result = result:gsub( '%.', '' ):gsub( ',', '.' )
if nombre:match( '%d+,%d%d%d,%d%d%d%f[%D]' ) -- type 1,234,567  
+
elseif result:match( '%d,%d%d%d,%d' ) -- type 1,234,567 ou 1.234,567,8
or nombre:match( '%d+,%d%d%d%.%d+' )  -- type 1,234.5
+
or result:match( '%d,%d%d%d%.%d' )  -- format anglo-saxon type 1,234.5   
--or nombre:match( '%d+,%d00$' ) -- type 1,200
+
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( ',', '' )
nombre = nombre:gsub( ',', '' )
+
else
elseif nombre:match( '%d+%.%d%d%d,%d' ) or nombre:match( '%d+%.%d%d%d%.%d%d%d%f[%D]' ) then
+
result = result:gsub( ',', '.' )
-- formant germanique type 1.234,5
 
nombre = nombre:gsub( '%.', '' ):gsub( ',', '.' )
 
else
 
nombre = nombre:gsub( ',', '.' )
 
end
 
 
end
 
end
 
end
 
end
 
 
return nombre
+
return result
 
end
 
end
  
Ligne 56 : Ligne 97 :
 
-- _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 )
+
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 )
elseif type( num ) ~= 'string' then
+
elseif type( num ) ~= 'string' or num == '' then
 
return num
 
return num
 
end
 
end
 
 
local moins, entier, fraction = num:match( '^(%-?)(%d+)%.?(%d*)$' )
+
-- séparation exposant
 +
local n, exponent = num:match( '^([-%d.]+)e([+-]?%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
 +
exponent = ' ×10' .. exponent
 +
else
 +
exponent = ''
 +
end
 +
 +
-- arrondi
 +
decimals = tonumber( params.decimals )
 +
round = tonumber( params.round ) or decimals
 +
if round then
 +
local mult = 10 ^ round
 +
num = tostring( math.floor( num * mult + 0.5 ) / mult )
 +
end
 +
 +
local moins, entier, fraction = num:match( '^(%-?)(%d*)%.?(%d*)$' )
 
if not entier then
 
if not entier then
 
return num
 
return num
Ligne 72 : Ligne 141 :
 
end
 
end
 
 
if entier:len() > 3 then
+
if entier == '' then
local ini = math.fmod( entier:len(), 3 )
+
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)', '\194\160%1' )
 
entier = ( entier:sub( 1, ini ) or '') .. entier:sub( ini + 1 ):gsub( '(%d%d%d)', '\194\160%1' )
 
end
 
end
if fraction ~= '' then
+
if fraction ~= '' or ( decimals and decimals > 0 ) then
fraction = ',' .. fraction:gsub( '(%d%d%d)', '%1\194\160' ):gsub( '\194\160$', '' )
+
if decimals and decimals > #fraction then
 +
fraction = fraction .. string.rep( '0', decimals - #fraction )
 +
end
 +
if #fraction > 4 then
 +
fraction = ',' .. fraction:gsub( '(%d%d%d)', '%1\194\160' ):gsub( '\194\160$', '' )
 +
else
 +
fraction = ',' .. fraction
 +
end
 
end
 
end
+
 
return moins .. entier .. fraction
+
return moins .. entier .. fraction .. exponent
 
end
 
end
  
 
---
 
---
-- formatNum transforme les nombres d'une chaine en chaine formatée suivant les conventions du français
+
-- formatNombre transforme un nombre formaté ou non en chaine formatée suivant les convention du français.
function p.formatNum( num )
+
-- si la chaine n'est pas reconnu comme un nombre, elle n'est pas modifiée.
if type( num ) == 'number' then
+
function p.formatNombre( num, round, decimals )
return p._formatNum( num )
+
return p.formatNum{ p.parseNombre( num ), round, decimals }
elseif type( num ) == 'string' then
 
return num:gsub( '%-?%d*%.?%d+', p.formatNombre )
 
end
 
 
end
 
end
  
function p.formatNombre( nombre )
+
--- formatNombres transforme tous les nombres d'une chaine en nombre formaté suivant les conventions du français.
return p._formatNum( p.parseNombre( nombre ) )
+
function p.formatNombres( nombres, round, decimals )
 +
if type( nombres ) == 'number' then
 +
return p.formatNum( nombres, round, decimals )
 +
elseif type( nombres ) == 'string' then
 +
-- retire les chiffres des strip marker
 +
local strip, i = {}, 0
 +
nombres = nombres:gsub(
 +
'UNIQ%-%-%a+%-%x%x%x%x%x%x%x%x%-QINU',
 +
function ( marker )
 +
i = i + 1
 +
strip[ tostring( i ) ] = marker
 +
return 'UNIQ^' .. i .. '¤QINU'
 +
end
 +
)
 +
--  formatage proprement dit
 +
nombres = p.sanitizeNum( nombres )
 +
nombres =  nombres:gsub(
 +
'%-?%f[%d.,^][%d., ]+%f[%D]',
 +
function ( n )
 +
return p.formatNombre( n, round, decimals )
 +
end
 +
)
 +
-- réintroduction des strip marker
 +
nombres = nombres:gsub( 'UNIQ^(%d+)¤QINU', strip )
 +
return nombres
 +
else
 +
return ''
 +
end
 
end
 
end
  
 
function p.parseUnit( texte )
 
function p.parseUnit( texte )
if trim( texte ) then
+
local toParse = p.sanitizeNum( texte )
local toParse = texte
+
if toParse ~= '' then
 
local result
 
local result
 +
local specificArgs = {
 +
['à'] = 'à',
 +
et = 'et',
 +
ou = 'ou',
 +
['–'] = '–', -- demi cadratin
 +
['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
 +
['+'] = '+',
 +
['−'] = '−', ['-'] = '−', -- signe moins et tiret
 +
['×'] = '×', x = '×', ['*'] = '×',
 +
['××'] = '××', xx = '××', ['**'] = '××',
 +
}
 +
 
-- valeur numérique
 
-- valeur numérique
local nombre = toParse:match( '^%-?%f[%d][%d., \194\160]*%d%f[%D]' ) or false
+
local match, capture = toParse:match( '^((%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
if nombre then
+
result = { capture or false }
toParse = toParse:sub( nombre:len() + 1 )
+
if match then
 +
toParse = toParse:sub( match:len() + 1 )
 +
 +
-- fraction
 +
match, capture = toParse:match( '^(([+-]?%d* ?/ ?%d+)%s*)' )
 +
if not match then
 +
match, capture = mw.ustring.match( toParse, '^(([¼-¾⅐-⅞])%s*)' )
 +
end
 +
if match then
 +
if capture:match( '^/' ) then
 +
local n = result[1]:match( ' (%d+)$' ) or ''
 +
result[1] = result[1]:sub( 1, -1 - #n )
 +
result.fraction = n .. capture
 +
else
 +
result.fraction = capture
 +
end
 +
toParse = toParse:sub( match:len() + 1 )
 +
end
 +
 +
-- lien avec un deuxième nombre
 +
local match2, conj, num = mw.ustring.match( toParse, '^(([àetouM+/−x*×±–-]+) ?(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
 +
if match2 and specificArgs[ conj ]
 +
and not ( specificArgs[ conj ] == '×' and mw.ustring.match( toParse, '^[×x] ?10 ?e') ) then
 +
result[ specificArgs[ conj ] ] = num
 +
toParse = toParse:sub( match2:len() + 1 )
 +
end
 +
if result['+'] or result['×'] then
 +
match2, conj, num = mw.ustring.match( toParse, '^(([x*×−-]) ?(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
 +
if match2 then
 +
if specificArgs[ conj ] == '×' then
 +
result['××'] = num
 +
else
 +
result['−'] = num
 +
end
 +
toParse = toParse:sub( match2:len() + 1 )
 +
end
 +
end
 
end
 
end
result = { nombre }
 
 
 
 
-- 10 exposant  ( \195\151 = ×, signe multiplié)
 
-- 10 exposant  ( \195\151 = ×, signe multiplié)
local match, e = toParse:match( '^(%s*[x\195]\151?10e(%-?%d+))' )
+
match, capture = toParse:match( '^(%s*e(%-?%d+)%s*)' )
 +
if not match then
 +
match, capture = toParse:match( '^(%s*[x\195]\151?10e(%-?%d+)%s*)' )
 +
end
 
if match then
 
if match then
table.insert( result, 'e' )
+
result.e = capture
table.insert( result, e )
 
 
toParse = toParse:sub( match:len() + 1 )
 
toParse = toParse:sub( match:len() + 1 )
 
end
 
end
 
 
 
-- unités
 
-- unités
toParse = toParse:gsub( '', '.' )
+
if Data.unit[ toParse ] or mw.ustring.match( toParse, '^%a+$' ) or toParse:match( '%b<>' ) then
local match, unit, exp = mw.ustring.match( toParse, '^(%s*%.?([%/]+)(%-?%d*))' )
+
table.insert( result, toParse )
while match and unit:len() < 5 do
+
toParse = ''
table.insert( result, unit )
+
elseif toParse ~= '' then
table.insert( result, exp )
+
local unit, exp
toParse = toParse:sub( match:len() + 1 )
+
toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
match, unit, exp = mw.ustring.match( toParse, '^(%s*%.?([%a°/]+)(%-?%d*))' )
+
repeat
 +
-- unité contenant un lien
 +
match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/%[%]]*%b[][^%s%d/]*) ?(%-?%d*)%s*)' )
 +
if not match then
 +
-- unité ne contenant pas de lien
 +
match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/]+) ?(%-?%d*)%s*)' )
 +
end
 +
if match then
 +
if unit:match( '%-$' ) and exp ~= '' then
 +
unit = unit:gsub( '%-$', '' )
 +
exp = '-' .. exp
 +
elseif exp == '-' then
 +
unit = match
 +
exp = ''
 +
end
 +
table.insert( result, unit )
 +
table.insert( result, exp )
 +
toParse = toParse:sub( match:len() + 1 )
 +
end
 +
until toParse == '' or not match
 
end
 
end
if trim( toParse ) then
+
 +
if toParse == '' then
 +
return result
 +
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 }
else
+
end
return result
 
end  
 
 
else
 
else
 
return { }
 
return { }
Ligne 136 : Ligne 308 :
 
end
 
end
  
function p._unite( args )
+
---
local wiki = { }
+
-- nomUtnit retourne le nom français du code d'une unité et de son exposant.
local sep = ''
+
-- 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.
local nombre = trim( args[1] )
+
function p.nomUnit( unit, exposant )
local unit = trim( args[2] )
+
if not dataSuccess or type( unit ) ~= 'string' then
local exp = trim( args.e )
+
return 1, false
 +
end
 
 
if nombre then
+
-- nettoyage des liens et balise HTML
nombre = nombre
+
unit = unit:gsub( '^/' , '' )
-- remplacement des signes moins par des tirets pour facilité les traitements ultérieurs
+
if unit:match( '%[' ) then
:gsub( '%−%f[%d]', '-') -- U+2212
+
local Delink = require( 'Module:Delink' )
:gsub( '&minus;%f[%d]', '-') -- html &minus;
+
unit = Delink._delink{ unit }
:gsub( '\226\128[\146\147]%f[%d]', '-') -- U+2212, U+2213 (tiret numérique et demi-cadratin)
+
end
-- remplacement des espaces insécable par des espace simple
+
if unit:match( '<' ) then
:gsub( '\194\160', ' ' )
+
unit = unit:gsub( '%b<>', '' )
 
end
 
end
 
 
if nombre and not unit and not exp and nombre:match('[^%d,. -]') then
+
-- récupère le nom de l'unité
args = p.parseUnit( nombre )
+
local unitTab = Data.unit[ unit ]
nombre = args[1]
+
local unitPrefix = { nom = '' }
unit = args[2]
+
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
 
end
 
 
-- formatage du nombre
+
-- assemble les deux partie
if nombre then
+
if type( unitTab ) == 'table' and type( unitTab.nom ) == 'string' then
nombre = nombre:gsub( '%-?%f[%d][%d., ]+%f[%D]', p.formatNombre )
+
return unitPrefix.nom .. unitTab.nom .. exposant
table.insert( wiki, nombre )
+
elseif unit:match( '[/%d]' ) then
sep = ' '
+
-- ce n'est pas du texte simple, on anule l'infobule
 +
return 1, false
 +
else
 +
return unit .. exposant
 +
end
 +
end
 +
 
 +
function p._unite( args )
 +
-- remplacement de certains caractères, pour simplifier les pattern
 +
local nombre = p.sanitizeNum( args[1] )
 +
if nombre == '' then
 +
nombre = nil
 +
else
 +
-- formatage du nombre
 +
nombre = p.formatNombres( nombre, args.arrondi, args['décimales'] )
 
end
 
end
 
 
-- puissance de 10
+
local wiki = { nombre }
local i = 1
+
if not exp and unit == '10' or unit == 'e' then
+
-- fraction
exp = args[3]
+
if args.fraction then
i = 2
+
local nom, den = args.fraction:match( '^(.-)/(.+)$' )
unit = trim( args[4] )
+
if nom then
 +
if nom:match( '^[ %dn()=+-]+$' ) 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;">' .. nom .. '</sup>'
 +
den = '<sub style="font-size: 70%; vertical-align: 0em;">' .. den .. '</sub>'
 +
end
 +
args.fraction = nom .. '⁄' .. den
 +
end
 +
table.insert( wiki, ' ' .. args.fraction )
 
end
 
end
if exp then
+
if #wiki > 0 then
+
-- à, et, ou, ×, – (tiret cadratin)
table.insert( wiki, '\194\160×\194\16010<sup>' .. exp .. '</sup>' )
+
local specificArgs = { '–', 'à', 'et', 'ou', '×', '××', '±' }
else
+
for _, name in ipairs( specificArgs ) do
table.insert( wiki, '10<sup>' .. exp .. '</sup>' )
+
local v = trim( args[ name ] )
 +
if v then
 +
v = p.formatNombres( v )
 +
if name == '–' and nombre and nombre:match( '^[^−]' ) and v:match( '^[^−]' ) then
 +
-- pas d'espace pour le tiret cadratin entre deux nombres positifs
 +
table.insert( wiki, '' )
 +
elseif name == '××' then
 +
table.insert( wiki, ' × ' )
 +
else
 +
table.insert( wiki, ' ' .. name .. ' ' )
 +
end
 +
table.insert( wiki, v )
 
end
 
end
sep = ' '
 
 
end
 
end
 
 
 
-- unités
 
-- unités
 +
local i = 1
 +
local unit = trim( args[ 2 * i ] )
 +
local units = ''
 +
local nomUnits, par = {}, false
 
while unit do
 
while unit do
if unit == '°' then
+
local exp = p.parseNombre( args[ 2 * i + 1 ] )
sep = ''
+
local sep = ''
end
+
-- gestion des exposants
table.insert( wiki, sep )
+
local expUnit = ''
table.insert( wiki, unit )
+
if exp == '' then
exp = trim( args[ 2 * i + 1 ] )
+
if unit:sub( -2 ) == '²' then
if exp then
+
exp = '2'
table.insert( wiki, '<sup>' .. exp:gsub( '%-', '−' ) .. '</sup>' )
+
unit = unit:sub( 1, -3 )
 +
elseif unit:sub( -2 ) == '³' 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
 
end
 +
-- gestion de la séparation des unités et des unités en dénominateur
 +
if units ~= '' then
 +
if unit:sub( 1, 1 ) == '/' then
 +
sep = '/'
 +
unit = unit:sub( 2 )
 +
if not par then
 +
par = true
 +
table.insert( nomUnits, 'par' )
 +
end
 +
else
 +
sep = '\194\160'  -- point médian désactivé : '⋅\194\160' 
 +
if exp:match( '^-' ) and not par then
 +
par = true
 +
table.insert( nomUnits, 'par' )
 +
end
 +
end
 +
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
 +
table.insert( nomUnits, p.nomUnit( unit, exp ) )
 
i = i + 1
 
i = i + 1
 
unit = trim( args[ 2 * i ] )
 
unit = trim( args[ 2 * i ] )
sep = '⋅'
 
 
end
 
end
 +
 +
-- conversion
 +
local unitNameString = nomUnits[1] and table.concat( nomUnits, ' ' ) or ''
 +
unitNameString = mw.ustring.gsub( unitNameString, '(%a)s%f[%A]', '%1' )
 +
local multiple = 1
 +
local convertTable = Data.convert[ unitNameString ]
 +
if not convertTable and #unitNameString > 5 then
 +
-- gesion des multiples (Kilo, méga, mili...)
 +
local prefix = Data.prefix[ unitNameString:sub( 1, 4 ) ] or Data.prefix[ unitNameString:sub( 1, 5 ) ]
 +
local _, par = unitNameString:find( ' par ' )
 +
local prefix2
 +
if par then
 +
prefix2 = Data.prefix[ unitNameString:sub( par + 1, 4 ) ] or Data.prefix[ unitNameString:sub( par + 1, 5 ) ]
 +
end
 +
if prefix and Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ) ] then
 +
convertTable = Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ) ]
 +
multiple = 10 ^ prefix.puissance
 +
elseif prefix2 and  Data.convert[ unitNameString:gsub( ' par ' .. prefix2.nom, '' ) ] then
 +
convertTable = Data.convert[ unitNameString:gsub( ' par ' .. prefix2.nom, '' ) ]
 +
multiple = 1 / 10 ^ prefix2.puissance
 +
elseif prefix and prefix2 and Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ] then
 +
convertTable = Data.convert[ unitNameString: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, v in ipairs( wiki ) do
 +
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 }
 +
table.insert( converted, nConverted .. ' '.. c[1] )
 +
end
 +
wiki[ i ] = '<span title="' .. table.concat( converted, ' ou ' ) ..'">' .. v ..'</span>'
 +
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
 +
table.insert( wiki, '<span style="display:inline-block; vertical-align:top; line-height:1em; font-size:80%; text-align:left;">' )
 +
table.insert( wiki, approximation .. '</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, '(' )
 +
table.insert( wiki, ')' )
 +
end
 +
table.insert( wiki, ' × 10<sup>' .. exposant .. '</sup>' )
 +
else
 +
table.insert( wiki, '10<sup>' .. exposant .. '</sup>' )
 +
end
 +
end
 +
 +
-- ajoute une abbréviation si le nom de l'unité est différent de l'unité (en retirant les espace qui peuvent être devenus insécables)
 +
if units and nomUnits[1] and mw.ustring.gsub( table.concat( nomUnits ), '[ \194\160]', '' ) ~= mw.ustring.gsub( units, '[ \194\160]', '' ) then
 +
units = string.format( ' <abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
 +
elseif units ~= '' and units ~= '°' then
 +
units = ' ' .. units
 +
end
 +
table.insert( wiki, units )
 
 
 
if #wiki > 0 then
 
if #wiki > 0 then
local result = table.concat( wiki ):gsub( '⋅/', '/' ) -- nettoyage cas particulier de parseunit.
+
local result = table.concat( wiki )
return '<span class="nowrap">' .. result .. '</span>'
+
if result:match( ' ' ) then
 +
return '<span class="nowrap">' .. result .. '</span>'
 +
else
 +
return result
 +
end
 
end
 
end
 
end
 
end
Ligne 206 : Ligne 563 :
 
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
 +
if type( frame.getParent ) == 'function' then
 
args = frame:getParent().args;
 
args = frame:getParent().args;
 +
else
 +
args = frame
 +
end
 
end
 
end
 
if args then
 
if args then
return p._unite( args )
+
if trim( args[1] ) then
 +
if args[1]:match('[^%d,. -]') then
 +
local tempArgs = p.parseUnit( trim( args[1] ) )
 +
if not ( args[2] and tempArgs[2] ) then
 +
for k, v in pairs( tempArgs ) do
 +
args[k] = v
 +
end
 +
end
 +
end
 +
if args[2] and not Data.unit[ args[2] ] and not args[3] and mw.text.trim( args[2] ):match('[%d/ -]') then
 +
local tempArgs = p.parseUnit( trim( 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
 +
-- args alias
 +
args['x'] = args['×'] -- lettre x → signe multiplié
 +
if args['+'] then
 +
args['−'] = args['−'] or args['-'] -- tiret → signe moins
 +
else
 +
args['–'] = args['–'] or args['-'] -- tiret → demi-cadratin
 +
end
 +
local cat = ''
 +
if addErrorCat then
 +
cat = errorCat
 +
end
 +
return p._unite( args ) .. cat
 
end
 
end
 
end
 
end
  
 
return p
 
return p

Version actuelle datée du 29 mai 2017 à 22:44

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'] = 'ₜ',
	}
--- Copie de Outils.trim acceptant les nombres.
local function trim( texte )
	if type( texte ) == 'string' then
		texte = texte:gsub( '^%s*(%S?.-)%s*$', '%1' )
		if texte ~= '' then
			return texte
		end
	elseif type( texte ) == 'number' then
		return tostring( texte )
	end
end

function p.sanitizeNum( nombre )
	if type( nombre ) == 'number' then
		return tostring( nombre )
	elseif trim( nombre ) then
		local result = nombre
			-- trim
			:gsub( '^%s*(.*)%f[%s]%s*$', '%1' )
			-- remplacement des signes moins ou demi-cadratin par un tiret
			:gsub( '%−%f[%d]', '-')  -- U+2212
			:gsub( '&minus;%f[%d]', '-')  -- html &minus;
			: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
			:gsub( '\194\160', ' ' )
		result = mw.ustring.gsub( result, '[ -  ]', ' ' ) -- U+2002 à U+220A et U+202F
		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 ''
		elseif not result:match( '^%-?[%d., ]*%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
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.]+)e([+-]?%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
		exponent = ' ×10' .. exponent
	else
		exponent = ''
	end
	
	-- arrondi
	decimals = tonumber( params.decimals )
	round = tonumber( params.round ) or decimals
	if round then
		local mult = 10 ^ round 
		num = tostring( math.floor( num * mult + 0.5 ) / mult )
	end
	
	local moins, entier, fraction = 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)', '\194\160%1' )
	end
	if fraction ~= '' or ( decimals and decimals > 0 ) then
		if decimals and decimals > #fraction then
			fraction = fraction .. string.rep( '0', decimals - #fraction )
		end
		if #fraction > 4 then
			fraction = ',' .. fraction:gsub( '(%d%d%d)', '%1\194\160' ):gsub( '\194\160$', '' )
		else
			fraction = ',' .. fraction
		end
	end

	return moins .. entier .. fraction .. 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, 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, decimals )
	elseif type( nombres ) == 'string' then
		-- retire les chiffres des strip marker
		local strip, i = {}, 0
		nombres = nombres:gsub( 
			'UNIQ%-%-%a+%-%x%x%x%x%x%x%x%x%-QINU',
			function ( marker )
				i = i + 1
				strip[ tostring( i ) ] = marker
				return 'UNIQ^' .. i .. '¤QINU'
			end
		)
		--  formatage proprement dit
		nombres = p.sanitizeNum( nombres )
		nombres =  nombres:gsub( 
			'%-?%f[%d.,^][%d., ]+%f[%D]', 
			function ( n )
				return p.formatNombre( n, round, decimals )
			end
		)
		-- réintroduction des strip marker
		nombres = nombres:gsub( 'UNIQ^(%d+)¤QINU', strip )
		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
			['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
			['+'] = '+',
			['−'] = '−', ['-'] = '−', -- signe moins et tiret
			['×'] = '×', x = '×', ['*'] = '×',
			['××'] = '××', xx = '××', ['**'] = '××',
		}
		
		-- valeur numérique
		local match, capture = toParse:match( '^((%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
		result = { capture or false }
		if match then
			toParse = toParse:sub( match:len() + 1 )
			
			-- fraction
			match, capture = toParse:match( '^(([+-]?%d* ?/ ?%d+)%s*)' )
			if not match then
				match, capture = mw.ustring.match( toParse, '^(([¼-¾⅐-⅞])%s*)' )
			end
			if match then
				if capture:match( '^/' ) then
					local n = result[1]:match( ' (%d+)$' ) or ''
					result[1] = result[1]:sub( 1, -1 - #n )
					result.fraction = n .. capture
				else
					result.fraction = capture
				end
				toParse = toParse:sub( match:len() + 1 )
			end
			
			-- lien avec un deuxième nombre
			local match2, conj, num = mw.ustring.match( toParse, '^(([àetouM+/−x*×±–-]+) ?(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
			if match2 and specificArgs[ conj ] 
				and not ( specificArgs[ conj ] == '×' and mw.ustring.match( toParse, '^[×x] ?10 ?e') ) then
				result[ specificArgs[ conj ] ] = num
				toParse = toParse:sub( match2:len() + 1 )
			end
			if result['+'] or result['×'] then
				match2, conj, num = mw.ustring.match( toParse, '^(([x*×−-]) ?(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
				if match2 then
					if specificArgs[ conj ] == '×' then
						result['××'] = num
					else
						result['−'] = num
					end
					toParse = toParse:sub( match2:len() + 1 )
				end
			end
		end
		
		-- 10 exposant   ( \195\151 = ×, signe multiplié)
		match, capture = toParse:match( '^(%s*e(%-?%d+)%s*)' )
		if not match then
			match, capture = toParse:match( '^(%s*[x\195]\151?10e(%-?%d+)%s*)' )
		end
		if match then
			result.e = capture
			toParse = toParse:sub( match:len() + 1 )
		end
		
		-- unités
		if Data.unit[ toParse ] or mw.ustring.match( toParse, '^%a+$' ) or toParse:match( '%b<>' ) then
			table.insert( result, toParse )
			toParse = ''
		elseif toParse ~= '' then
			local unit, exp
			toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
			repeat
				-- unité contenant un lien
				match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/%[%]]*%b[][^%s%d/]*) ?(%-?%d*)%s*)' )
				if not match then
					-- unité ne contenant pas de lien
					match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/]+) ?(%-?%d*)%s*)' )
				end
				if match then
					if unit:match( '%-$' ) and exp ~= '' then
						unit = unit:gsub( '%-$', '' )
						exp = '-' .. exp
					elseif exp == '-' then
						unit = match
						exp = ''
					end
					table.insert( result, unit )
					table.insert( result, exp )
					toParse = toParse:sub( match:len() + 1 )
				end
			until toParse == '' or not match
		end
		
		if toParse == '' then
			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

---
-- nomUtnit 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. 
function p.nomUnit( unit, exposant )
	if not dataSuccess or type( unit ) ~= 'string' then
		return 1, 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
	
	-- 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 unitPrefix.nom .. unitTab.nom .. exposant 
	elseif unit:match( '[/%d]' ) then
		-- ce n'est pas du texte simple, on anule l'infobule
		return 1, false
	else
		return unit .. exposant
	end
end

function p._unite( args )
	-- remplacement de certains caractères, pour simplifier les pattern
	local nombre = p.sanitizeNum( args[1] )
	if nombre == '' then
		nombre = nil
	else	
		-- formatage du nombre
		nombre = p.formatNombres( nombre, args.arrondi, args['décimales'] )
	end
	
	local wiki = { nombre }
	
	-- fraction
	if args.fraction then
		local nom, den = args.fraction:match( '^(.-)/(.+)$' )
		if nom then
			if nom:match( '^[ %dn()=+-]+$' ) 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;">' .. nom .. '</sup>'
				den = '<sub style="font-size: 70%; vertical-align: 0em;">' .. den .. '</sub>'
			end
			args.fraction = nom .. '⁄' .. den
		end
		table.insert( wiki, ' ' .. args.fraction )
	end
	
	-- à, et, ou, ×, – (tiret cadratin)
	local specificArgs = { '–', 'à', 'et', 'ou', '×', '××', '±'	}
	for _, name in ipairs( specificArgs ) do
		local v = trim( args[ name ] )
		if v then
			v = p.formatNombres( v )
			if name == '–' and nombre and nombre:match( '^[^−]' ) and v:match( '^[^−]' ) then
				-- pas d'espace pour le tiret cadratin entre deux nombres positifs
				table.insert( wiki, '–' )
			elseif name == '××' then
				table.insert( wiki, ' × ' )
			else
				table.insert( wiki, ' ' .. name .. ' ' )
			end
			table.insert( wiki, v )
		end
	end
	
	-- unités
	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
			if unit:sub( -2 ) == '²' then
				exp = '2'
				unit = unit:sub( 1, -3 )
			elseif unit:sub( -2 ) == '³' 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 units ~= '' then
			if unit:sub( 1, 1 ) == '/' then
				sep = '/'
				unit = unit:sub( 2 )
				if not par then
					par = true
					table.insert( nomUnits, 'par' )
				end
			else
				sep = '\194\160'  -- point médian désactivé : '⋅\194\160'  
				if exp:match( '^-' ) and not par then
					par = true
					table.insert( nomUnits, 'par' )
				end
			end
		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
		table.insert( nomUnits, p.nomUnit( unit, exp ) )
		i = i + 1
		unit = trim( args[ 2 * i ] )
	end
	
	-- conversion
	local unitNameString = nomUnits[1] and table.concat( nomUnits, ' ' ) or ''
	unitNameString = mw.ustring.gsub( unitNameString, '(%a)s%f[%A]', '%1' )
	local multiple = 1
	local convertTable = Data.convert[ unitNameString ]
	if not convertTable and #unitNameString > 5 then
		-- gesion des multiples (Kilo, méga, mili...)
		local prefix = Data.prefix[ unitNameString:sub( 1, 4 ) ] or Data.prefix[ unitNameString:sub( 1, 5 ) ]
		local _, par = unitNameString:find( ' par ' )
		local prefix2
		if par then
			prefix2 = Data.prefix[ unitNameString:sub( par + 1, 4 ) ] or Data.prefix[ unitNameString:sub( par + 1, 5 ) ]
		end
		if prefix and Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ) ] then
			convertTable = Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ) ]
			multiple = 10 ^ prefix.puissance
		elseif prefix2 and  Data.convert[ unitNameString:gsub( ' par ' .. prefix2.nom, '' ) ] then
			convertTable = Data.convert[ unitNameString:gsub( ' par ' .. prefix2.nom, '' ) ]
			multiple = 1 / 10 ^ prefix2.puissance
		elseif prefix and prefix2 and Data.convert[ unitNameString:gsub( '^' .. prefix.nom, '' ):gsub( ' par ' .. prefix2.nom, '' ) ] then 
			convertTable = Data.convert[ unitNameString: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, v in ipairs( wiki ) do
			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 }
					table.insert( converted, nConverted .. ' '.. c[1] )
				end
				wiki[ i ] = '<span title="' .. table.concat( converted, ' ou ' ) ..'">' .. v ..'</span>'
			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
		table.insert( wiki, '<span style="display:inline-block; vertical-align:top; line-height:1em; font-size:80%; text-align:left;">' )
		table.insert( wiki, approximation .. '</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, '(' )
				table.insert( wiki, ')' )
			end
			table.insert( wiki, ' × 10<sup>' .. exposant .. '</sup>' )
		else
			table.insert( wiki, '10<sup>' .. exposant .. '</sup>' )
		end
	end
	
	-- ajoute une abbréviation si le nom de l'unité est différent de l'unité (en retirant les espace qui peuvent être devenus insécables)
	if units and nomUnits[1] and mw.ustring.gsub( table.concat( nomUnits ), '[ \194\160]', '' ) ~= mw.ustring.gsub( units, '[ \194\160]', '' ) then
		units = string.format( ' <abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
	elseif units ~= '' and units ~= '°' then
		units = ' ' .. units
	end
	table.insert( wiki, units )
	
	if #wiki > 0 then
		local result = table.concat( wiki )
		if result:match( ' ' ) then
			return '<span class="nowrap">' .. result .. '</span>'
		else
			return result
		end
	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
		if trim( args[1] ) then
			if args[1]:match('[^%d,. -]') then
				local tempArgs = p.parseUnit( trim( args[1] ) )
				if not ( args[2] and tempArgs[2] ) then
					for k, v in pairs( tempArgs ) do
						args[k] = v
					end
				end
			end
			if args[2] and not Data.unit[ args[2] ] and not args[3] and mw.text.trim( args[2] ):match('[%d/ -]') then
				local tempArgs = p.parseUnit( trim( 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
		-- args alias
		args['x'] = args['×'] -- lettre x → signe multiplié
		if args['+'] then
			args['−'] = args['−'] or args['-'] -- tiret → signe moins
		else
			args['–'] = args['–'] or args['-'] -- tiret → demi-cadratin
		end
		local cat = ''
		if addErrorCat then
			cat = errorCat
		end
		return p._unite( args ) .. cat
	end
end

return p