Module:Unité : Différence entre versions

De Lagny-sur-Marne Wiki
Aller à : navigation, rechercher
(parseNombre : modification pour prendre se comporté comme le modèle actuel pour les nombres avec virgule et points)
(retouche de la modification précédente)
 
(19 révisions intermédiaires par le même utilisateur non affichées)
Ligne 12 : Ligne 12 :
 
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 27 : Ligne 37 :
 
if type( nombre ) == 'number' then
 
if type( nombre ) == 'number' then
 
return tostring( nombre )
 
return tostring( nombre )
elseif type( nombre ) == 'string' then
+
elseif trim( nombre ) then
 
local result = nombre
 
local result = nombre
 
-- trim
 
-- trim
Ligne 37 : Ligne 47 :
 
-- remplacement des espaces insécable par des espace simple
 
-- remplacement des espaces insécable par des espace simple
 
:gsub( '\194\160', ' ' )
 
:gsub( '\194\160', ' ' )
 +
result = mw.ustring.gsub( result, '[ -  ]', ' ' ) -- U+2002 à U+220A et U+202F
 
return result
 
return result
 
else  
 
else  
Ligne 67 : Ligne 78 :
 
-- gestion des points et des virgules
 
-- gestion des points et des virgules
 
if result:match( '[.,]' ) then
 
if result:match( '[.,]' ) then
if result:match( '%d%.%d%d%d%.%d%' ) then
+
if result:match( '%d%.%d%d%d%.%d' ) then
 
-- type 12.345.678
 
-- type 12.345.678
 
result = result:gsub( '%.', '' ):gsub( ',', '.' )
 
result = result:gsub( '%.', '' ):gsub( ',', '.' )
Ligne 86 : 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' or num == '' then
 
elseif type( num ) ~= 'string' or num == '' then
 
return num
 
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
 
end
 
 
Ligne 108 : Ligne 147 :
 
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 133 : Ligne 164 :
 
-- 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, 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
 +
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  
 
else  
 
return ''
 
return ''
Ligne 152 : Ligne 203 :
 
if toParse ~= '' then
 
if toParse ~= '' then
 
local result
 
local result
local specificArgs = { ['à'] = 'à', et = 'et', ['–'] = '–', ['±'] = '±' }
+
local specificArgs = {
 +
['à'] = 'à',
 +
et = 'et',
 +
ou = 'ou',
 +
['–'] = '–', -- demi cadratin
 +
['±'] = '±', ['+-'] = ', ['+/-'] = '±',
 +
['+'] = '+',
 +
['−'] = '−', ['-'] = '−', -- signe moins et tiret
 +
['×'] = '×', 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( '^((%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' )
 +
result = { capture or false }
 
if match then
 
if match then
 
toParse = toParse:sub( match:len() + 1 )
 
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 = { capture or false }
 
 
 
 
-- 10 exposant  ( \195\151 = ×, signe multiplié)
 
-- 10 exposant  ( \195\151 = ×, signe multiplié)
Ligne 172 : Ligne 268 :
 
 
 
-- unités
 
-- unités
toParse = toParse:gsub( '', '.' ):gsub( '²', '2' ):gsub( '³', '3' )
+
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 do -- and unit:len() < 5
+
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 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 toParse == '' then
 
if toParse == '' then
 
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 229 : Ligne 340 :
 
end
 
end
 
end
 
end
end
+
end
  
 
-- récupère le nom de l'exposant
 
-- récupère le nom de l'exposant
Ligne 252 : Ligne 363 :
  
 
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 = { nombre }
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
 +
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>'
+
table.insert( wiki, ' × ' )
else
+
else
exposant = '10<sup>' .. exposant .. '</sup>'
+
table.insert( wiki, ' ' .. name .. ' ' )
 +
end
 +
table.insert( wiki, v )
 
end
 
end
 
end
 
end
 
 
-- unités
+
-- unités
 
local i = 1
 
local i = 1
 
local unit = trim( args[ 2 * i ] )
 
local unit = trim( args[ 2 * i ] )
Ligne 302 : Ligne 416 :
 
local exp = 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 exp == '' then
 
if unit:sub( -2 ) == '²' then
 
if unit:sub( -2 ) == '²' then
Ligne 311 : Ligne 427 :
 
end
 
end
 
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 325 : Ligne 447 :
 
end
 
end
 
end
 
end
local expUnit = ''
+
-- remplacement de l'unité par son symbole
if #exp > 0 then
+
if Data.unit[ unit ] then
expUnit = '<sup>' .. exp:gsub( '^-', '−') .. '</sup>'  -- remplace le tiret par un vrai signe moins
+
-- unit = Data.unit[ unit ].symbole
 +
-- désactivé car ne gère pas les multiple tel mL
 
end
 
end
 
units = units .. sep .. unit .. expUnit
 
units = units .. sep .. unit .. expUnit
Ligne 335 : Ligne 458 :
 
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
 +
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
 
end
 
 
local wiki = { nombre }
+
-- puissance de 10
table.insert( wiki, approximation )  
+
local exposant = trim( args.e )
table.insert( wiki, exposant )
+
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)
 
-- 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
 
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 )
+
units = string.format( ' <abbr class=abbr title="%s">%s</abbr>', table.concat( nomUnits, ' ' ), units )
 +
elseif units ~= '' and units ~= '°' then
 +
units = ' ' .. units
 
end
 
end
 
table.insert( wiki, units )
 
table.insert( wiki, units )
 
 
 
if #wiki > 0 then
 
if #wiki > 0 then
local result = table.concat( wiki, ' ' )
+
local result = table.concat( wiki )
 
if result:match( ' ' ) then
 
if result:match( ' ' ) then
 
return '<span class="nowrap">' .. result .. '</span>'
 
return '<span class="nowrap">' .. result .. '</span>'
Ligne 375 : Ligne 571 :
 
end
 
end
 
if args then
 
if args then
if not ( args.e or args.et or args['à'] or args['±'] ) and trim( args[1] ) then
+
if trim( args[1] ) then
if not args[2] and args[1]:match('[^%d,. -]') then
+
if args[1]:match('[^%d,. -]') then
args = p.parseUnit( trim( args[1] ) )
+
local tempArgs = p.parseUnit( trim( args[1] ) )
elseif args[2] and not args[3] and args[2]:match('[%d./ -]') then
+
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] ) )
 
local tempArgs = p.parseUnit( trim( args[2] ) )
if tempArgs[1] == false then
+
args[2] = false
tempArgs[1] = args[1]
+
if tempArgs[1] ~= false then
else
+
table.insert( tempArgs, 1, false )
table.insert( tempArgs, 1, args[1] )
+
end
 +
for k, v in pairs( tempArgs ) do
 +
if args[k] and v then
 +
addErrorCat = true
 +
end
 +
args[k] = args[k] or v
 
end
 
end
args = tempArgs
 
 
end
 
end
 
end
 
end
return p._unite( args )
+
-- 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