Module:Unité
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 --- Copie de Outils.trim acceptant les nombres. local function trim( texte ) if type( texte ) == 'string' then texte = texte:gsub( '^%s*(.*)%f[%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( '−%f[%d]', '-') -- html − :gsub( '\226\128[\146\147]%f[%d]', '-') -- U+2212, U+2213 (tiret numérique et demi-cadratin) -- remplacement des espaces insécable par des espace simple :gsub( '\194\160', ' ' ) 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 ) if type( num ) == 'number' then num = tostring( num ) elseif type( num ) ~= 'string' or num == '' then return num 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 ~= '' then fraction = ',' .. fraction:gsub( '(%d%d%d)', '%1\194\160' ):gsub( '\194\160$', '' ) end return moins .. entier .. fraction end --- -- 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 --- -- 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( nombre ) return p._formatNum( p.parseNombre( nombre ) ) end --- formatNombres transforme tous les nombres d'une chaine en nombre formaté suivant les conventions du français. function p.formatNombres( texte ) if type( texte ) == 'number' then return p.formatNum( texte ) elseif type( texte ) == 'string' then return texte:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre ) 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 = '×', ['*'] = '×', } -- valeur numérique local match, capture = toParse:match( '^((%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' ) if match then toParse = toParse:sub( match:len() + 1 ) end result = { capture or false } -- lien avec un deuxième nombre local match, conj, num = mw.ustring.match( toParse, '^(([àetouM+/−x*×±–-]+) ?(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' ) if match and specificArgs[ conj ] and not ( specificArgs[ conj ] == '×' and mw.ustring.match( toParse, '^[×x] ?10 ?e') ) then result[ specificArgs[ conj ] ] = num toParse = toParse:sub( match:len() + 1 ) end if result['+'] or result['×'] then match, conj, num = mw.ustring.match( toParse, '^(([x*×−-]) ?(%-?%f[%d.,][%d., ]*%d%f[%D])%s*)' ) if match then if specificArgs[ conj ] == '×' then result['××'] = num else result['−'] = num end toParse = toParse:sub( match:len() + 1 ) 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 toParse = toParse:gsub( '⋅', '.' ):gsub( '²', '2' ):gsub( '³', '3' ) local unit, exp local unitRegex = '^(%.?(/?[%aà°±]+) ?(%-?%d*)%s*)' match, unit, exp = mw.ustring.match( toParse, unitRegex ) while match do -- and unit:len() < 5 table.insert( result, unit ) table.insert( result, exp ) toParse = toParse:sub( match:len() + 1 ) match, unit, exp = mw.ustring.match( toParse, unitRegex ) 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 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, nbNombre = nombre:gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre ) end local wiki = { nombre } -- à, et, ou, ×, – (tiret cadratin) local specificArgs = { '–', 'à', 'et', 'ou', '×', '××', '±' } for _, name in ipairs( specificArgs ) do local v = trim( args[ name ] ) if v then v = p.sanitizeNum( v ):gsub( '%-?%f[%d.,][%d., ]+%f[%D]', p.formatNombre ) 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, '–' .. v ) elseif name == '××' then table.insert( wiki, ' × ' .. v ) else table.insert( wiki, ' ' .. name .. ' ' .. v ) 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 -- 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 = '' 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 units ~= '' then if unit:sub( 1, 1 ) == '/' then 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 local expUnit = '' if #exp > 0 then expUnit = '<sup>' .. exp:gsub( '^-', '−') .. '</sup>' -- remplace le tiret par un vrai signe moins end units = units .. sep .. unit .. expUnit table.insert( nomUnits, p.nomUnit( unit, exp ) ) i = i + 1 unit = trim( args[ 2 * i ] ) 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 not ( args.e or args.et or args['à'] or args['±'] ) and trim( args[1] ) then if args[1]:match('[^%d,. -]') then if args[2] then local tempArgs = p.parseUnit( trim( args[1] ) ) if not tempArgs[2] then for k, v in pairs( tempArgs ) do args[k] = v end end else args = p.parseUnit( trim( args[1] ) ) end end if args[2] and not args[3] and args[2]:match('[%d./ -]') then local tempArgs = p.parseUnit( trim( args[2] ) ) if tempArgs[1] == false then tempArgs[1] = args[1] else table.insert( tempArgs, 1, args[1] ) end args = tempArgs 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 return p._unite( args ) end end return p