Actions

Module

« Unité » : différence entre les versions

De Wikimanche

wm>Zebulon84
(ajoute des infobulles avec le nom de l'unité.)
(Mise à jour module)
(35 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
local p = {}
local p = {}
-- local Delink = require( 'Module:Delink' ) -- chargé uniquement si nécessaire


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


local errorCat = '[[Catégorie:Page incorrectement traitée par le Module:Unité]]'
local addErrorCat = false
local supUnicode = { ['0'] = '⁰', ['1'] = '¹', ['2'] = '²', ['3'] = '³', ['4'] = '⁴', ['5'] = '⁵', ['6'] = '⁶', ['7'] = '⁷', ['8'] = '⁸', ['9'] = '⁹',
['+'] = '⁺', ['-'] = '⁻', ['='] = '⁼', ['('] = '⁽', [')'] = '⁾', ['n'] = 'ⁿ' }
local subUnicode = { ['0'] = '₀', ['1'] = '₁', ['2'] = '₂', ['3'] = '₃', ['4'] = '₄', ['5'] = '₅', ['6'] = '₆', ['7'] = '₇', ['8'] = '₈', ['9'] = '₉',
['+'] = '₊', ['-'] = '₋', ['='] = '₌', ['('] = '₍', [')'] = '₎',
['a'] = 'ₐ', ['e'] = 'ₑ', ['o'] = 'ₒ', ['x'] = 'ₓ', ['h'] = 'ₕ', ['k'] = 'ₖ', ['l'] = 'ₗ',
['m'] = 'ₘ', ['n'] = 'ₙ', ['p'] = 'ₚ', ['s'] = 'ₛ', ['t'] = 'ₜ',
}
--- 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 26 : Ligne 38 :
return tostring( nombre )
return tostring( nombre )
elseif type( nombre ) == 'string' then
elseif type( nombre ) == 'string' then
if nombre:match( '^%-?[%d.,]+$' ) then
return nombre
end
local result = nombre
local result = nombre
-- trim
:gsub( '^%s*(.*)%f[%s]%s*$', '%1' )
-- remplacement des signes moins ou demi-cadratin par un tiret
-- remplacement des signes moins ou demi-cadratin par un tiret
:gsub( '%−%f[%d]', '-')  -- U+2212
:gsub( '%−%f[%d]', '-')  -- U+2212
Ligne 35 : Ligne 48 :
-- remplacement des espaces insécable par des espace simple
-- remplacement des espaces insécable par des espace simple
:gsub( '\194\160', ' ' )
:gsub( '\194\160', ' ' )
:gsub( ' ', ' ' )
:gsub( '\226\128[\128-\138\175]', ' ' ) -- U+2002 à U+200A et U+202F
-- trim
:gsub( '^%s*(%S?.-)%s*$', '%1' )
return result
return result
else  
else
return ''
return ''
end
end
Ligne 57 : Ligne 74 :
elseif not result:match( '^%-?[%d., ]*%d$' ) then
elseif not result:match( '^%-?[%d., ]*%d$' ) then
return nombre
return nombre
end
end
end
end
 
-- suppression espaces
-- suppression espaces
result = result:gsub( ' ', '' )
result = result:gsub( ' ', '' )
 
-- gestion des points et des virgules
if result:match( '[.,]' ) then
if result:match( '[.,]' ) then
if result:match( '%d+,%d%d%d,%d%d%d%f[%D]' ) -- type 1,234,567  
if result:match( '%d%.%d%d%d%.%d' ) then
or result:match( '%d+,%d%d%d%.%d+' )  -- type 1,234.5
-- type 12.345.678
--or nombre:match( '%d+,%d00$' ) -- type 1,200
result = result:gsub( '%.', '' ):gsub( ',', '.' )
elseif result:match( '%d,%d%d%d,%d' ) -- type 1,234,567 ou 1.234,567,8
or result:match( '%d,%d%d%d%.%d' )  -- format anglo-saxon type 1,234.5
or result:match( '%d%.%d%d%d,%d' ) -- type 1.123,56 (utilisé en exemple pour sépararer les décimales avec l'ancien modèle unité ou formatnum)
then
then
-- format anglo-saxon
result = result:gsub( ',', '' )
result = result:gsub( ',', '' )
elseif result:match( '%d+%.%d%d%d,%d' ) or result:match( '%d+%.%d%d%d%.%d%d%d%f[%D]' ) then
-- formant germanique type 1.234,5
result = result:gsub( '%.', '' ):gsub( ',', '.' )
else
else
result = result:gsub( ',', '.' )
result = result:gsub( ',', '.' )
end
end
end
end
 
return result
return result
end
end
Ligne 84 : Ligne 101 :
-- _formantNum transforme un nombre ou une chaine représentant un nombre en chaine formatée suivant les conventions du français
-- _formantNum transforme un nombre ou une chaine représentant un nombre en chaine formatée suivant les conventions du français
-- si le paramètre ne représente pas un nombre lua il est retourné sans modification
-- si le paramètre ne représente pas un nombre lua il est retourné sans modification
function p._formatNum( num )
-- Le paramètre peut être transmis sous forme de table pour ajouter des options :
-- * round : arrondi à n chiffre après la virgule (peut être négatif)
-- * decimals : nombre de décimales affichées (peut être négatif, dans ce cas équivalent à round)
-- * noHtml : n'utilise pas de balise HTML pour affiché les puissance de 10 (pour pouvoir être utilisé en title)
function p.formatNum( num )
local params = {}
if type( num ) == 'table' then
params = num
num = params[1]
end
if type( num ) == 'number' then
if type( num ) == 'number' then
num = tostring( num )
num = tostring( num )
Ligne 90 : Ligne 116 :
return num
return num
end
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
exponent = ' ×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, fraction = num:match( '^(%-?)(%d*)%.?(%d*)$' )
local moins, entier, fraction = num:match( '^(%-?)(%d*)%.?(%d*)$' )
if not entier then
if not entier then
return num
return num
end
end
 
if moins == '-' then
if moins == '-' then
moins = '−' -- signe moins (U+2212)
moins = '−' -- signe moins (U+2212)
end
end
 
if entier == '' then
if entier == '' then
entier = '0'
entier = '0'
Ligne 106 : Ligne 155 :
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
end


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


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


--- formatNombres transforme tous les nombres d'une chaine en nombre formaté suivant les conventions du français.
--- formatNombres transforme tous les nombres d'une chaine en nombre formaté suivant les conventions du français.
function p.formatNombres( texte )
function p.formatNombres( nombres, round, decimals )
if type( texte ) == 'number' then
if type( nombres ) == 'number' then
return p.formatNum( texte )
return p.formatNum( nombres, round, decimals )
elseif type( texte ) == 'string' then
elseif type( nombres ) == 'string' then
return texte:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre )
-- retire les chiffres des strip marker
else  
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 )
local formatN = function ( n )
return p.formatNombre( n, round, decimals )
end
nombres = nombres
:gsub( '%-?%f[%d.,][%d., ]*%de[+-]?%d+', formatN )
:gsub( '%-?%f[%d.,][%d., ]*%d', formatN )
 
-- réintroduction des strip marker
nombres = nombres:gsub( 'UNIQ^(%d+)¤QINU', strip )
return nombres
else
return ''
return ''
end
end
Ligne 150 : Ligne 212 :
if toParse ~= '' then
if toParse ~= '' then
local result
local result
local specificArgs = { ['à'] = 'à', et = 'et', ['–'] = '–', ['±'] = '±' }
local specificArgs = {
['à'] = 'à',
et = 'et',
ou = 'ou',
['/'] = '/',
['–'] = '–', ['-'] = '–', -- demi cadratin et tiret
['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
['+'] = '+',
['−'] = '−', -- signe moins
['×'] = '×', x = '×', ['*'] = '×',
['××'] = '××', xx = '××', ['**'] = '××',
}
 
-- valeur numérique
-- valeur numérique
local match, capture = toParse:match( '^((%-?%f[%d.,][%d., \194\160]*%d%f[%D])%s*)' )
local match, capture = toParse:match( '^(([%d., ]+%f[^d%(])%s*)' )
local prefix
if not match then
-- cas ou le nombre est remplcé par un ou plusieurs points d'interrogation
match, prefix = toParse:match( '^((%?+)%s*)' )
end
if not match 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)
match, prefix, capture = toParse:match( '^(([%a]+[.,]?[: ]* )([+-]?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
end
if not match then
-- 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*)' )
end
if not match then
-- cas ou le nombre est précédé par un symbole ≤, ≥ ou ≈
match, prefix, capture = toParse:match( '^((\226\137[\164\165\136] ?)([+-]?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
end
result = { capture or false, prefix = prefix }
if match then
if match then
toParse = toParse:sub( match:len() + 1 )
toParse = toParse:sub( match:len() + 1 )
-- point de suspensions (ex π = 3.14159...)
match = toParse:match( '^…%s*' )
if not match then
match, capture = toParse:match( '^%.%.%.%s*' )
end
if match then
result[1] = result[1] .. '…'
toParse = toParse:sub( match:len() + 1 )
end
-- fraction
match, capture = toParse:match( '^((%d*/%d+)%s*)' )
if not match then
match, capture = toParse:match( '^((\194[\188-\190])%s*)' ) -- ¼ à ¾
end
if not match then
match, capture = toParse:match( '^((\226\133[\144-\158])%s*)' ) -- ⅐ à ⅞
end
if match then
if 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( 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 = { capture or false }
 
-- 10 exposant  ( \195\151 = ×, signe multiplié)
-- 10 exposant  ( \195\151 = ×, signe multiplié)
match, capture = toParse:match( '^(%s*e(%-?%d+)%s*)' )
match, capture = toParse:match( '^(%s*e(%-?%d+)%s*)' )
Ligne 168 : Ligne 306 :
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 unit, exp
table.insert( result, toParse )
local unitRegex = '^(%.?(/?[%aà°±]+) ?(%-?[%d%.,]*)%s*)'
toParse = ''
match, unit, exp = mw.ustring.match( toParse, unitRegex )
elseif toParse ~= '' then
while match and unit:len() < 5 do
local unit, exp
if specificArgs[ unit ] then
toParse = toParse:gsub( '²', '2' ):gsub( '³', '3' )
result[ specificArgs[ unit ] ] = exp
repeat
else
-- unité contenant un lien
table.insert( result, unit )
match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/%[%]]*%b[][^%s%d/]*) ?(%-?%d*)%s*)' )
table.insert( result, exp )
if not match then
end
-- unité ne contenant pas de lien
toParse = toParse:sub( match:len() + 1 )
match, unit, exp = mw.ustring.match( toParse, '^((/?[^%s%d/]+) ?(%-?%d*)%s*)' )
match, unit, exp = mw.ustring.match( toParse, unitRegex )
end
if not match then
-- l/100 km
match, unit, exp = mw.ustring.match( toParse, '^((/100 ?[^%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
if Data.unit[ unit ] or mw.ustring.match( unit, '[%a€£$«»]' ) then
table.insert( result, unit )
table.insert( result, exp )
toParse = toParse:sub( match:len() + 1 )
else
break
end
end
until toParse == '' or not match
end
end
if toParse == '' then
if toParse == '' then
if #result > 1 and result[ #result ] == '' then
result[ #result ] = nil
end
return result
return result
else
else
-- une partie de la chaine n'a pas pu être décodée, on retourne la chaine originale
-- une partie de la chaine n'a pas pu être décodée, on retourne la chaine originale
addErrorCat = true
return { texte }
return { texte }
end
end
Ligne 197 : Ligne 361 :
---
---
-- nomUtnit retourne le nom français du code d'une unité et de son exposant.
-- 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.  
-- 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, exp )
function p.nomUnit( unit, exposant )
if not dataSuccess or type( unit ) ~= 'string' then
if not dataSuccess or type( unit ) ~= 'string' then
return 1, false
return 1, false
end
end
-- nettoyage des liens et balise HTML
unit = unit:gsub( '^/' , '' )
unit = unit:gsub( '^/' , '' )
if unit:match( '%[' ) then
local Delink = require( 'Module:Delink' )
unit = Delink._delink{ unit }
end
if unit:match( '<' ) then
unit = unit:gsub( '%b<>', '' )
end
-- récupère le nom de l'unité
local unitTab = Data.unit[ unit ]
local unitTab = Data.unit[ unit ]
local unitPrefix = { nom = '' }
local unitPrefix = { nom = '' }
Ligne 209 : Ligne 384 :
unitPrefix = Data.prefix[ unit:sub( 1, 1 ) ]
unitPrefix = Data.prefix[ unit:sub( 1, 1 ) ]
if not ( unitTab and unitPrefix ) then
if not ( unitTab and unitPrefix ) then
-- pour µ, Ki, Mi, Gi... qui sont codé sur deux octets
unitTab = Data.unit[ unit:sub( 3 ) ]
unitTab = Data.unit[ unit:sub( 3 ) ]
unitPrefix = Data.prefix[ unit:sub( 1, 2 ) ]
unitPrefix = Data.prefix[ unit:sub( 1, 2 ) ]
Ligne 216 : Ligne 392 :
end
end
end
end
-- récupère le nom de l'exposant
if trim( exposant ) then
local exp = tonumber( exposant )
exp = exp and Data.exposant[ math.abs( exp ) ]
exposant = exp or ' puissance ' .. exposant
else
exposant = ''
end
-- assemble les deux partie
if type( unitTab ) == 'table' and type( unitTab.nom ) == 'string' then
if type( unitTab ) == 'table' and type( unitTab.nom ) == 'string' then
exp = tonumber( exp )
return unitPrefix.nom .. unitTab.nom .. exposant
exp = exp and math.abs( exp )
elseif unit:match( '[/%d]' ) then
local exposant = exp and Data.exposant[exp] or ''
-- ce n'est pas du texte simple, on anule l'infobule
return unitPrefix.nom .. unitTab.nom .. exposant  
return 1, false
else
else
return 1, false
return unit .. exposant
end
end
end
end


function p._unite( args )
function p._unite( args )
local sep = ''
-- remplacement de certains caractères, pour simplifier les pattern
local nombre, nbNombre, nombre2, nbNombre2
local nombre = p.sanitizeNum( args[1] )
nombre = p.sanitizeNum( args[1] )
if nombre == '' then
if nombre == '' then
nombre = nil
nombre = nil
else
else
-- formatage du nombre
-- formatage du nombre
nombre, nbNombre = nombre:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre )
nombre = p.formatNombres( nombre, args.arrondi, args['décimales'] )
end
end
 
-- et / à '–'
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 intervalle = trim( args.et ) and ' et '  
 
or trim( args['à'] ) and ' à '
-- fraction
or args['–'] and '–'
if args.fraction then
if nombre and intervalle then
local nom, den = args.fraction:match( '^(.-)/(.+)$' )
nombre2 = nombre2 or trim( args['à'] ) or trim( args.et ) or trim( args['–'] )
if nom then
if nombre2 then  
if nom:match( '^[ %dn()=+-]+$' ) and den:match( '^[ %daeoxhklmnpst()=+-]$' ) then
nombre2 = p.sanitizeNum( nombre2 )  
nom = nom:gsub( '[%dn()=+-]', supUnicode )
nombre2, nbNombre2 = nombre2:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre )
den = den:gsub( '[%daeoxhklmnpst()=+-]', subUnicode )
if intervalle == '' and nombre:sub( 1, 2 ) == '−' then
else
intervalle = ' '
nom = '<sup style="font-size: 70%; vertical-align: 0.4em;">' .. nom .. '</sup>'
den = '<sub style="font-size: 70%; vertical-align: 0em;">' .. den .. '</sub>'
end
end
args.fraction = nom .. '⁄' .. den
end
end
if nombre then
table.insert( wiki, '\194\160' )
end
table.insert( wiki, args.fraction )
end
end
 
-- ±
-- à, et, ou, ×, – (tiret cadratin)
local approximation
local specificArgs = { '–', 'à', 'et', 'ou', '/', '×', '××', '±' }
if trim( args['±'] ) then
for _, name in ipairs( specificArgs ) do
approximation = '±\194\160' .. p.formatNombre( args['±'] )
local v = trim( args[ name ] )
end
if v then
v = p.formatNombres( v )
-- puissance de 10
if name == '' and nombre and nombre:match( '^[^−]' ) and v:match( '^[^−]' ) then
local exposant = trim( args.e )
-- pas d'espace pour le tiret cadratin entre deux nombres positifs
if exposant then
table.insert( wiki, '–' )
if nombre then
elseif name == '/' then
exposant = '×\194\16010<sup>' .. exposant .. '</sup>'
-- espace demi-cadratin (U+2002 ou &ensp;) avec /
else
table.insert( wiki, ' / ' )
exposant = '10<sup>' .. exposant .. '</sup>'
elseif name == '××' then
table.insert( wiki, '\194\160×\194\160' )
elseif name == '×' or name == '±' then
table.insert( wiki, '\194\160' .. name .. '\194\160' )
else
table.insert( wiki, ' ' .. name .. ' ' )
end
table.insert( wiki, v )
end
end
end
end
 
-- unités
-- analyse de l'unité pour la conversion (mais ne sera affiché qu'après l'incertitude + et - séparé
local i = 1
local i = 1
local unit = trim( args[ 2 * i ] )
local unit = trim( args[ 2 * i ] )
Ligne 275 : Ligne 474 :
local nomUnits, par = {}, false
local nomUnits, par = {}, false
while unit do
while unit do
local exp = tonumber( p.parseNombre( args[ 2 * i + 1 ] ) )
local exp = p.parseNombre( args[ 2 * i + 1 ] )
local sep = ''
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 units ~= '' then
if unit:sub( 1, 1 ) == '/' then
if unit:sub( 1, 1 ) == '/' then
sep = '/'
unit = unit:sub( 2 )
if not par then
if not par then
par = true
par = true
Ligne 284 : Ligne 500 :
end
end
else
else
sep = '\194\160'  -- point médian désactivé : '⋅\194\160'
sep = '\194\160'  -- point médian désactivé : '⋅\194\160'
if exp and not par and exp < 0 then
par = true
table.insert( nomUnits, 'par' )
end
end
end
end
end
local expUnit = exp and '<sup>' .. exp .. '</sup>' or ''
if exp:match( '^-' ) and not par then
par = true
table.insert( nomUnits, '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
units = units .. sep .. unit .. expUnit
table.insert( nomUnits, p.nomUnit( unit, exp ) )
table.insert( nomUnits, p.nomUnit( unit, exp ) )
Ligne 297 : Ligne 517 :
unit = trim( args[ 2 * i ] )
unit = trim( args[ 2 * i ] )
end
end
 
if units == '°' then
-- conversion
nombre = nombre and nombre .. '°'
local unitNameString = nomUnits[1] and table.concat( nomUnits, ' ' ) or ''
nombre2 = nombre2 and nombre2 .. '°'
unitNameString = mw.ustring.gsub( unitNameString, '(%a)s%f[%A]', '%1' )
approximation = approximation and approximation .. '°'
local multiple = 1
units = nil
local convertTable = Data.convert[ unitNameString ]
end
if not convertTable and #unitNameString > 5 then
if intervalle then
-- gesion des multiples (Kilo, méga, mili...)
nombre = nombre .. intervalle .. nombre2
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
end
if convertTable then
local wiki = { nombre }
if type( convertTable[1] ) ~= 'table' then
table.insert( wiki, approximation )  
convertTable = { convertTable }
table.insert( wiki, exposant )
end
if nomUnits[1] then
for i, v in ipairs( wiki ) do
units = string.format( '<abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
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 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
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 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>' )
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, '\194\160× 10<sup>' .. exposant .. '</sup>' )
else
table.insert( wiki, '10<sup>' .. exposant .. '</sup>' )
end
end
end
table.insert( wiki, units )
 
if units ~= '' then
if #wiki > 0 then
local sep = '\194\160'
local result = table.concat( wiki, ' ' )
if not ( nombre or args.fraction or exposant ) then
if result:match( ' ' ) then
sep = ''
return '<span class="nowrap">' .. result .. '</span>'
else
else
return result
local symbole = Data.unit[ units ] and Data.unit[ units ].symbole
if symbole == '°' or symbole == '′' or symbole == '″' then
sep = ''
end
end
-- ajoute une abréviation si le nom de l'unité est différent de l'unité (en retirant 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
units = string.format( '<abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
end
end
table.insert( wiki, sep .. units )
end
if #wiki > 0 then
return table.concat( wiki )
end
end
end
end
Ligne 328 : Ligne 632 :
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
if #args == 1  
args[1] = trim( args[1] ) or false
and not ( args.e or args.et or args['à'] or args['±'] )
if args[1] then
and trim( args[1] )  
if args[1]:match('[^%d,. -]') then
and args[1]:match('[^%d,. -]')
local tempArgs = p.parseUnit( args[1] )
then
if not ( args[2] and tempArgs[2] ) then
args = p.parseUnit( trim( args[1] ) )
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] and 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
-- 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 then
cat = errorCat
end
end
return p._unite( args )
return p._unite( args ) .. cat
end
end
end
end


return p
return p

Version du 29 mars 2020 à 00:53

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 type( nombre ) == 'string' then
		if nombre:match( '^%-?[%d.,]+$' ) then
			return nombre
		end
		local result = nombre
			-- 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', ' ' )
			:gsub( '&nbsp;', ' ' )
			:gsub( '\226\128[\128-\138\175]', ' ' ) -- U+2002 à U+200A et U+202F
			-- 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 ''
		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
-- 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
		exponent = ' ×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, 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 = 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, 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 )
		local formatN = function ( n )
			return p.formatNombre( n, round, decimals )
		end
		nombres = nombres
			:gsub( '%-?%f[%d.,][%d., ]*%de[+-]?%d+', formatN )
			:gsub( '%-?%f[%d.,][%d., ]*%d', formatN )

		-- 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 et tiret
			['±'] = '±', ['+-'] = '±', ['+/-'] = '±',
			['+'] = '+',
			['−'] = '−', -- signe moins
			['×'] = '×', x = '×', ['*'] = '×',
			['××'] = '××', xx = '××', ['**'] = '××',
		}

		-- valeur numérique
		local match, capture = toParse:match( '^(([%d., ]+%f[^d%(])%s*)' )
		local prefix
		if not match then
			-- cas ou le nombre est remplcé par un ou plusieurs points d'interrogation
			match, prefix = toParse:match( '^((%?+)%s*)' )
		end
		if not match 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)
			match, prefix, capture = toParse:match( '^(([%a]+[.,]?[: ]* )([+-]?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
		end
		if not match then
			-- 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*)' )
		end
		if not match then
			-- cas ou le nombre est précédé par un symbole ≤, ≥ ou ≈
			match, prefix, capture = toParse:match( '^((\226\137[\164\165\136] ?)([+-]?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
		end
		result = { capture or false, prefix = prefix }
		if match then
			toParse = toParse:sub( match:len() + 1 )

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

			-- fraction
			match, capture = toParse:match( '^((%d*/%d+)%s*)' )
			if not match then
				match, capture = toParse:match( '^((\194[\188-\190])%s*)' ) -- ¼ à ¾
			end
			if not match then
				match, capture = toParse:match( '^((\226\133[\144-\158])%s*)' ) -- ⅐ à ⅞
			end
			if match then
				if 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( 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 not match then
					-- l/100 km
					match, unit, exp = mw.ustring.match( toParse, '^((/100 ?[^%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
					if Data.unit[ unit ] or mw.ustring.match( unit, '[%a€£$«»]' ) then
						table.insert( result, unit )
						table.insert( result, exp )
						toParse = toParse:sub( match:len() + 1 )
					else
						break
					end
				end
			until toParse == '' or not match
		end

		if toParse == '' then
			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

---
-- 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 = { args.prefix or '', nombre }   -- prefix est un paramètre interne défini par p.parseUnit, utile notamment lorsque {{unité}} est utilisé dans les infobox

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

		if nombre then
			table.insert( wiki, '\194\160' )
		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
				-- 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
				table.insert( wiki, '\194\160' .. name .. '\194\160' )
			else
				table.insert( wiki, ' ' .. name .. ' ' )
			end
			table.insert( wiki, 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
			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'
			end
		end
		if exp:match( '^-' ) and not par then
			par = true
			table.insert( nomUnits, '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
		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 }
					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
				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 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>' )
	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, '\194\160× 10<sup>' .. exposant .. '</sup>' )
		else
			table.insert( wiki, '10<sup>' .. exposant .. '</sup>' )
		end
	end

	if units ~= '' then
		local sep = '\194\160'
		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 retirant 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
			units = string.format( '<abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
		end
		table.insert( wiki, 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
		args[1] = trim( args[1] ) or false
		if args[1] 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] and 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
		-- 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 then
			cat = errorCat
		end
		return p._unite( args ) .. cat
	end
end

return p