Module:Unité

La bibliothèque libre.
Documentation du module [voir] [modifier] [purger]
La documentation de ce module Scribunto écrit en Lua est incluse depuis sa sous-page de documentation.

Utilisation[modifier]

Fonctions exportables[modifier]

  • unite( frame ) – implémente le modèle unité. Les paramètres sont pris soit au niveau du modèle appelant le module via #invoke, soit directement dans la table fournie lorsque la fonction est appelée depuis un autre module. Essaye de parser les deux premiers paramètres pour facilité la saisie (par exemple fonction avec p.unite{ '1.23 ±0.05 e5 m/s-2' }) ;
  • _unite( args ) – affiche l'unité à partir des paramètres classiques du modèle Unité (exemple p._unite{ '1.23', 'm', '/s', '-2', ['±'] = '0.05', e='5' }) ;
  • formatNombres( texte ) – formate tous les nombres de la chaine fournie suivant les conventions du français ;
  • formatNombre( nombre ) – transforme un nombre formaté ou non en chaine formatée suivant les conventions du français ; si la chaine n'est pas reconnue comme un nombre, elle n'est pas modifiée ;
  • _formatNum( num ) – transforme un number, ou une chaine correspondant à un number en chaine formatée suivant les conventions du français ; si le paramètre ne représente pas un number lua il est retourné sans modification ;
  • parseNombre( nombre ) – transforme si possible une chaine formatée en un chaine interprétable par tonumber() (retourne une chaine pour éviter les arrondis éventuels de lua) ; les chaines non reconnues sont retournées sans modification.

Autres fonctions[modifier]

  • sanitizeNum( nombre ) – transforme les signes moins en tiret, les espaces insécables en espace simple (simplifie les pattern ultérieures) ;
  • parseUnit( texte ) – essaye de séparer une chaine en différents paramètres du modèle unité ;
  • nomUnit( unit, exposant ) – retourne si possible le nom de l'unité et son exposant en toute lettre.

Modules externes et autres éléments dont ce module a besoin pour fonctionner[modifier]

  • Module:Unité/Data – Liste d'unités et de multiples, avec leur abréviation et leur nom en toute lettre.
  • Module:Delink – Utilisé pour supprimer les liens des unités pour essayer de les reconnaitre.

Exemples[modifier]

Pour des exemples, voir la page de test de la Wikipédia en français permettant de tester diverses modifications apportées.

local p = {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return nombres
	else
		return ''
	end
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	local wiki = {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return p