Jump to content
 







Main menu
   


Navigation  



Main page
Contents
Current events
Random article
About Wikipedia
Contact us
Donate
 




Contribute  



Help
Learn to edit
Community portal
Recent changes
Upload file
 








Search  

































Create account

Log in
 









Create account
 Log in
 




Pages for logged out editors learn more  



Contributions
Talk
 

















Module:Val: Difference between revisions






Аԥсшәа
العربية

Azərbaycanca
تۆرکجه
Basa Bali

Беларуская (тарашкевіца)

Буряад
Català
Чӑвашла
Čeština
الدارجة
Euskara
فارسی
Gaeilge
Galego

Hausa
Հայերեն
ि
Ilokano
Bahasa Indonesia
Italiano
Jawa

Lietuvių
Ligure
ि
Македонски


 / Mìng-dĕ̤ng-nḡ
Мокшень

Na Vosa Vakaviti


Oʻzbekcha / ўзбекча

Português
Română
Scots
Shqip
Simple English
Slovenščina
کوردی
Српски / srpski
Suomi
Svenska
ி
Taqbaylit
 
Tetun

Türkçe
Українська
اردو
Vèneto
Tiếng Vit


 

Edit links
 









Module
Talk
 

















Read
View source
View history
 








Tools
   


Actions  



Read
View source
View history
 




General  



What links here
Related changes
Upload file
Special pages
Permanent link
Page information
Get shortened URL
Download QR code
Wikidata item
 




Print/export  



Download as PDF
Printable version
 
















Appearance
   

 





Help

Permanently protected module

From Wikipedia, the free encyclopedia
 


Browse history interactively
 Previous edit
Content deleted Content added
No edit summary
use i18n from Module:Val/sandbox added by User:Moroboshi
 
(36 intermediate revisions by 6 users not shown)
Line 1: Line 1:

-- For Template:Val, output a number and optional unit.

local p = {}

-- Format options include scientific and uncertainty notations.

local getArgs

local delimit_groups = require('Module:Gapnum').groups



local numdot = '.' -- decimal mark (use ',' for Italian)

function p.main(frame)

local numsep = ',' -- group separator (use ' ' for Italian)

if not getArgs then

local mtext = {

getArgs = require('Module:Arguments').getArgs

-- Message and other text that should be localized.

['mt-bad-exponent'] = 'exponent parameter (<b>e</b>)',

['mt-parameter'] = 'parameter ',

['mt-not-number'] = 'is not a valid number',

['mt-cannot-range'] = 'cannot use a range if the first parameter includes "e"',

['mt-need-range'] = 'needs a range in parameter 2',

['mt-should-range'] = 'should be a range',

['mt-cannot-with-e'] = 'cannot be used if the first parameter includes "e"',

['mt-not-range'] = 'does not accept a range',

['mt-cannot-e'] = 'cannot use e notation',

['mt-too-many-parameter'] = 'too many parameters',

['mt-need-number'] = 'need a number after the last parameter because it is a range.',

['mt-ignore-parameter4'] = 'Val parameter 4 ignored',

['mt-val-not-supported'] = 'Val parameter "%s=%s" is not supported',

['mt-invalid-scale'] = 'Unit "%s" has invalid scale "%s"',

['mt-both-u-ul'] = 'unit (<b>u</b>) and unit with link (<b>ul</b>) are both specified, only one is allowed.',

['mt-both-up-upl'] = 'unit per (<b>up</b>) and unit per with link (<b>upl</b>) are both specified, only one is allowed.',

}


local data_module = 'Module:Val/units'

local convert_module = 'Module:Convert'


local function valerror(msg, nocat, iswarning)

-- Return formatted message text for an error or warning.

-- Can append "#FormattingError" to URL of a page with a problem to find it.

local anchor = '<span id="FormattingError"></span>'

local body, category

if nocat or mw.title.getCurrentTitle():inNamespaces(1, 2, 3, 5) then

-- No category in Talk, User, User_talk, or Wikipedia_talk.

category = ''

else

category = '[[Category:Pages with incorrect formatting templates use]]'

end

end

iswarning = false -- problems are infrequent so try showing large error so editor will notice

local args = getArgs(frame, {wrappers = 'Template:Val'})

if iswarning then

local number = {n=args[1], nend=args['end']}

body = '<sup class="noprint Inline-Template" style="white-space:nowrap;">' ..

local nocat = args.nocategory

'[[Template:Val|<span title="' ..

msg:gsub('"', '&quot;') ..

-- Error checking

'">warning</span>]]</sup>'

if not validnumber(args[1]) then

else

return valerror('first argument is not a valid number.',nocat)

body = '<strong class="error">' ..

'Error in &#123;&#123;[[Template:val|val]]&#125;&#125;: ' ..

msg ..

'</strong>'

end

end

return anchor .. body .. category

if args[2] and not validnumber(args[2]) then

end

return valerror('second argument is not a valid number.',nocat)


local range_types = {

-- No need for '&nbsp;' because nowrap applies to all output.

[","] = ", ",

["by"] = " by ",

["-"] = "–",

["–"] = "–",

["and"] = " and ",

["or"] = " or " ,

["to"] = " to " ,

["x"] = " × ",

["×"] = " × ",

["/"] = "/",

}

local range_repeat_unit = {

-- WP:UNIT wants unit repeated when a "multiply" range is used.

["x"] = true,

["×"] = true,

}


local function extract_item(index, numbers, arg)

-- Extract an item from arg and store the result in numbers[index].

-- If no argument or if argument is valid, return nil (no error);

-- otherwise, return an error message.

-- The stored result is:

-- * a table for a number (empty if there was no specified number); or

-- * a string for range text

-- Input like 1e3 is regarded as invalid for all except argument 1

-- which accepts e notation as an alternative to the 'e' argument.

-- Input group separators are removed.

local which = index

local function fail(msg)

local description

if which == 'e' then

description = mtext['mt-bad-exponent']

else

description = mtext['mt-parameter'] .. which

end

return description .. ' ' .. (msg or mtext['mt-not-number']) .. '.'

end

end

local result = {}

if args[3] and not validnumber(args[3]) then

local range = range_types[arg]

return valerror('third argument is not a valid number.',nocat)

if range then

if type(index) == 'number' and (index % 2 == 0) then

if index == 2 then

if numbers[1] and numbers[1].exp then

return fail(mtext['mt-cannot-range'])

end

numbers.has_ranges = true

else

if not numbers.has_ranges then

return fail(mtext['mt-need-range'])

end

end

numbers[index] = range

if range_repeat_unit[arg] then

-- Any "repeat" range forces unit (if any) to be repeated for all items.

numbers.isrepeat = true

end

return nil

end

return fail(mtext['mt-not-range'])

end

end

if numbers.has_ranges and type(index) == 'number' and (index % 2 == 0) then

-- Negative third param

return fail(mtext['mt-should-range'])

if args[3] and (not mw.ustring.find(args[3],'[−%-]') or mw.ustring.find(args[3],'^%D0$')) then

return valerror('third argument is not negative.',nocat)

end

end

ifargs.e and not validnumber(args.e) then

ifindex == 'e' then

local e = numbers[1] and numbers[1].exp

return valerror('exponent argument (<b>e</b>) is not a valid number.',nocat)

if e then

if arg then

return fail(mtext['mt-cannot-with-e'])

end

arg = e

which = 1

end

end

end

ifargs.u and args.ul then

ifarg and arg ~= '' then

arg = arg:gsub(numsep, '')

return valerror('unit (<b>u</b>) and units with link (<b>ul</b>) are both specified, only one is allowed.',nocat)

if numdot ~= '.' then

arg = arg:gsub(numdot, '.')

end

if arg:sub(1, 1) == '(' and arg:sub(-1) == ')' then

result.parens = true

arg = arg:sub(2, -2)

end

local a, b = arg:match('^(.+)[Ee](.+)$')

if a then

if index == 1 then

arg = a

result.exp = b

else

return fail(mtext['mt-cannot-e'])

end

end

local isnegative, propersign, prefix

local minus = '−'

prefix, arg = arg:match('^(.-)([%d.]+)$')

local value = tonumber(arg)

if not value then

return fail()

end

if arg:sub(1, 1) == '.' then

arg = '0' .. arg

end

if prefix == '' then

-- Ignore.

elseif prefix == '±' then

-- Display for first number, ignore for others.

if index == 1 then

propersign = '±'

end

elseif prefix == '+' then

propersign = '+'

elseif prefix == '-' or prefix == minus then

propersign = minus

isnegative = true

else

return fail()

end

result.clean = arg

result.sign = propersign or ''

result.value = isnegative and -value or value

end

end

numbers[index] = result

if args.up and args.upl then

return nil -- no error

return valerror('unit per (<b>up</b>) and units per with link (<b>upl</b>) are both specified, only one is allowed.',nocat)

end


local function get_args(numbers, args)

-- Extract arguments and store the results in numbers.

-- Return nothing (no error) if ok; otherwise, return an error message.

for index = 1, 99 do

local which = index

local arg = args[which] -- has been trimmed

if not arg then

which = 'e'

arg = args[which]

end

local msg = extract_item(which, numbers, arg)

if msg then

return msg

end

if which == 'e' then

break

end

if index > 19 then

return mtext['mt-too-many-parameter']

end

end

end

if numbers.has_ranges and (#numbers % 2 == 0) then

return mtext['mt-need-number']

end

end



local function get_scale(text, ucode)

-- Group arguments into related categories and unpack when needed

-- Return the value of text as a number, or throw an error.

local uncertainty = {upper=args[2], lower=args[3],

-- This supports extremely basic expressions of the form:

errend=args.errend,

-- a / b

upperend=args['+errend'], lowerend=args['-errend']}

-- a ^ b

local u_tbl = {u=args.ul or args.u, ul=args.ul ~= nil,

-- where a and b are numbers or 'pi'.

p=args.upl or args.up, pl=args.upl ~= nil}

local n = tonumber(text)

local misc_tbl = {e=args.e, pre=args.p, suf=args.s, fmt=args.fmt or '', nocat=args.nocategory}

if n then

return p._main(number,uncertainty,u_tbl,misc_tbl)

return n

end

n = text:gsub('pi', math.pi)

for _, op in ipairs({ '/', '^' }) do

local a, b = n:match('^(.-)' .. op .. '(.*)$')

if a then

a = tonumber(a)

b = tonumber(b)

if a and b then

if op == '/' then

return a / b

elseif op == '^' then

return a ^ b

end

end

break

end

end

error(string.format(mtext['mt-invalid-scale'], ucode, text))

end

end



local function get_builtin_unit(ucode, definitions)

function p._main(number,uncertainty,u_tbl,misc_tbl)

-- Return table of information for the specified built-in unit, or nil if not known.

-- format number

-- Each defined unit code must be followed by two spaces (not tab characters).

local fmt = misc_tbl.fmt

local n = delimit(number.n,fmt)

local _, pos = definitions:find('\n' .. ucode .. ' ', 1, true)

if pos then

local endline = definitions:find('%s*\n', pos)

if endline then

local result = {}

local n = 0

local text = definitions:sub(pos + 1, endline - 1):gsub('%s%s+', '\t')

for item in (text .. '\t'):gmatch('(%S.-)\t') do

if item == 'ALIAS' then

result.alias = true

elseif item == 'ANGLE' then

result.isangle = true

result.nospace = true

elseif item == 'NOSPACE' then

result.nospace = true

elseif item == 'SI' then

result.si = true

else

n = n + 1

if n == 1 then

local link, symbol = item:match('^%[%[([^|]+)|(.+)%]%]$')

if link then

result.symbol = symbol

result.link = link

n = 2

else

result.symbol = item

end

elseif n == 2 then

result.link = item

elseif n == 3 then

result.scale_text = item

result.scale = get_scale(item, ucode)

else

result.more_ignored = item

break

end

end

end

if result.si then

local s = result.symbol

if ucode == 'mc' .. s or ucode == 'mu' .. s then

result.ucode = 'µ' .. s -- unit code for convert should be this

end

end

if n >= 2 or (n >= 1 and result.alias) then

return result

end

-- Ignore invalid definition, treating it as a comment.

end

end

end



local function convert_lookup(ucode, value, scaled_top, want_link, si, options)

local e_10 = misc_tbl.e

local lookup = require(convert_module)._unit

return lookup(ucode, {

value = value,

scaled_top = scaled_top,

link = want_link,

si = si,

sort = options.sortable,

})

end



local function get_unit(ucode, value, scaled_top, options)

-- number suffix

local want_link = options.want_link

if number.nend then

if scaled_top then

n = n..number.nend

want_link = options.want_per_link

end

end

local data = mw.loadData(data_module)

local result = options.want_longscale and

get_builtin_unit(ucode, data.builtin_units_long_scale) or

get_builtin_unit(ucode, data.builtin_units)

local si, use_convert

if result then

if result.alias then

ucode = result.symbol

use_convert = true

end

if result.scale then

-- Setting si means convert will use the unit as given, and the sort key

-- will be calculated from the value without any extra scaling that may

-- occur if convert found the unit code. For example, if val defines the

-- unit 'year' with a scale and if si were not set, convert would also apply

-- its own scale because convert knows that a year is 31,557,600 seconds.

si = { result.symbol, result.link }

value = value * result.scale

end

if result.si then

ucode = result.ucode or ucode

si = { result.symbol, result.link }

use_convert = true

end

else

result = {}

use_convert = true

end

local convert_unit = convert_lookup(ucode, value, scaled_top, want_link, si, options)

result.sortkey = convert_unit.sortspan

if use_convert then

result.text = convert_unit.text

result.scaled_top = convert_unit.scaled_value

else

if want_link then

result.text = '[[' .. result.link .. '|' .. result.symbol .. ']]'

else

result.text = result.symbol

end

result.scaled_top = value

end

return result

end



local function makeunit(value, options)

-- If units are defined, load the unit submodule to create a string

-- Return table of information for the requested unit and options, or

if u_tbl.u then

-- return nil if no unit.

local makeunit = require('Module:Val/units')

options = options or {}

units = makeunit(u_tbl.u,{

local unit

link=u_tbl.ul,

local ucode = options.u

per=u_tbl.p,

local percode = options.per

per_link=u_tbl.pl})

if ucode then

unit = get_unit(ucode, value, nil, options)

elseif percode then

unit = { nospace = true, scaled_top = value }

else

return nil

end

end

local text = unit.text or ''

local sortkey = unit.sortkey

if percode then

local function bracketed(code, text)

return code:find('[*./]') and '(' .. text .. ')' or text

end

local perunit = get_unit(percode, 1, unit.scaled_top, options)

text = (ucode and bracketed(ucode, text) or '') ..

'/' .. bracketed(percode, perunit.text)

sortkey = perunit.sortkey

end

if not (unit.nospace or options.nospace) then

text = '&nbsp;' .. text

end

return { text = text, isangle = unit.isangle, sortkey = sortkey }

end



local function list_units(mode)

-- Uncertainty

-- Return wikitext to list the built-in units.

local unc

-- A unit code should not contain wikimarkup so don't bother escaping.

-- Upper and lower

local data = mw.loadData(data_module)

local uncU, uncL = uncertainty.upper, uncertainty.lower

local definitions = data.builtin_units .. data.builtin_units_long_scale

-- Whether or not the entire number needs to be wrapped in parentheses

local last_was_blank = true

-- true if:

local n = 0

---- the expontent parameter (e) is defined

local result = {}

---- AND

local function add(line)

---- no lower uncertainty is defined

if line == '' then

---- AND

last_was_blank = true

---- upper uncertainty is defined and contains no parentheses

local paren_wrap = misc_tbl.e and (not uncL and (uncU and not uncU:find('%(')))


-- boolean to be defined and used later

local paren_uncertainty

-- Upper is always used, so look for it first

if uncU then

-- Look for lower uncertainty

if uncL then

-- Load the sup/sub module

local mSu = require('Module:Su')._main

-- Format upper and lower

uncU = delimit(uncU,fmt)

uncL = delimit(uncL,fmt)

-- If no exponent is defined, and there are units, add them

if not e_10 and units then

uncU = uncU..units

uncL = uncL..units

end

-- Add the uncertainty suffixes here

uncU = uncU..(uncertainty.upperend or '')

uncL = uncL..(uncertainty.lowerend or '')


unc = '<span style="margin-left:0.3em;">'..mSu(uncU,uncL)..'</span>'

else

else

if last_was_blank and n > 0 then

-- Look for parentheses surrounding upper uncertainty

n = n + 1

local uncU_n = mw.ustring.match(uncU,('%((.+)%)')) or uncU

result[n] = ''

-- If no parens, use ±

if uncU == uncU_n then

unc = '<span style="margin-left:0.3em;margin-right:0.15em">±</span>'..delimit(uncU_n,fmt)..'</span>'

-- Otherwise tidy the number and put it back in parentheses

-- Indicate parentheses were used (for later)

else

unc = '('..delimit(uncU_n,fmt)..')'

paren_uncertainty = true

end

end

last_was_blank = false

-- Add error suffix

n = n + 1

if uncertainty.errend then

result[n] = line

unc = unc..uncertainty.errend

end

end

local si_prefixes = {

-- These are the prefixes recognized by convert; u is accepted for micro.

y = 'y',

z = 'z',

a = 'a',

f = 'f',

p = 'p',

n = 'n',

u = 'µ',

['µ'] = 'µ',

m = 'm',

c = 'c',

d = 'd',

da = 'da',

h = 'h',

k = 'k',

M = 'M',

G = 'G',

T = 'T',

P = 'P',

E = 'E',

Z = 'Z',

Y = 'Y',

}

local function is_valid(ucode, unit)

if unit and not unit.more_ignored then

assert(type(unit.symbol) == 'string' and unit.symbol ~= '')

if unit.alias then

if unit.link or unit.scale_text or unit.si then

return false

end

end

end

if unit.si then

-- Add units if no exponent argument

ifnot e_10 and units then

ifunit.scale_text then

unc = unc..units

return false

end

ucode = unit.ucode or ucode

local base = unit.symbol

if ucode == base then

unit.display = base

return true

end

local plen = #ucode - #base

if plen > 0 then

local prefix = si_prefixes[ucode:sub(1, plen)]

if prefix and ucode:sub(plen + 1) == base then

unit.display = prefix .. base

return true

end

end

else

unit.display = unit.symbol

return true

end

end

end

end

return false

end

end

local lookup = require(convert_module)._unit

-- Add units if no exponent argument and no parentheses for uncertainty

local function show_convert(ucode, unit)

if not e_10 and units and not paren_uncertainty then

-- If a built-in unit defines a scale or sets the SI flag, any unit defined in

n = n..units

-- convert is not used (the scale or SI prefix's scale is used for a sort key).

-- If there is no scale or SI flag, and the unit is not defined in convert,

-- the sort key may not be correct; this allows such units to be identified.

if not (unit.si or unit.scale_text) then

if mode == 'convert' then

unit.show = not lookup(unit.alias and unit.symbol or ucode).unknown

unit.show_text = 'CONVERT'

elseif mode == 'unknown' then

unit.show = lookup(unit.alias and unit.symbol or ucode).unknown

unit.show_text = 'UNKNOWN'

elseif not unit.alias then

-- Show convert's scale in square brackets ('[1]' for an unknown unit).

-- Don't show scale for an alias because it's misleading for temperature

-- and an alias is probably not useful for anything else.

local scale = lookup(ucode, {value=1, sort='on'}).scaled_value

if type(scale) == 'number' then

scale = string.format('%.5g', scale):gsub('e%+?(%-?)0*(%d+)', 'e%1%2')

else

scale = '?'

end

unit.show = true

unit.show_text = '[' .. scale .. ']'

end

end

end

end

for line in definitions:gmatch('([^\n]*)\n') do

-- If exponent defined, create 10<sup>e</sup>

local pos, _ = line:find(' ', 1, true)

-- Add units if they're defined

ife_10 then

ifpos then

local ucode = line:sub(1, pos - 1)

e_10 = '<span style="margin-left:0.25em;margin-right:0.15em">×</span>10<sup>'..delimit(misc_tbl.e)..'</sup>'

local unit = get_builtin_unit(ucode, '\n' .. line .. '\n')

if units then

if is_valid(ucode, unit) then

e_10 = e_10..units

show_convert(ucode, unit)

local flags, text

if unit.alias then

text = unit.symbol

else

text = '[[' .. unit.link .. '|' .. unit.display .. ']]'

end

if unit.isangle then

unit.nospace = nil -- don't show redundant flag

end

for _, f in ipairs({

{ 'alias', 'ALIAS' },

{ 'isangle', 'ANGLE' },

{ 'nospace', 'NOSPACE' },

{ 'si', 'SI' },

{ 'scale_text', unit.scale_text },

{ 'show', unit.show_text },

}) do

if unit[f[1]] then

local t = f[2]

if t:match('^%u+$') then

t = '<small>' .. t .. '</small>'

end

if flags then

flags = flags .. ' ' .. t

else

flags = t

end

end

end

if flags then

text = text .. ' • ' .. flags

end

add(ucode .. ' = ' .. text .. '<br />')

else

add(line .. ' ◆ <b>invalid definition</b><br />')

end

else

add(line)

end

end

else

e_10 = ''

end

end

return table.concat(result, '\n')

-- Table to concat in order of what goes where

local ret =

table.concat({

-- prefix

misc_tbl.pre or '',

-- opening parenthesis if needed

paren_wrap and '(' or '',

-- number

n,

-- uncertainties

unc or '',

-- closes parenthesis if needed

paren_wrap and ')' or '',

-- 10^e if needed

e_10,

-- suffix

misc_tbl.suf or ''

})

return ret

end

end



local delimit_groups = require('Module:Gapnum').groups

-- TODO: Add other format options

function delimit(n,fmt)

local function delimit(sign, numstr, fmt)

-- Return sign and numstr (unsigned digits or numdot only) after formatting.

local prefix,num

-- Four-digit integers are not formatted with gaps.

if not fmt then fmt = '' end

fmt = (fmt or ''):lower()


if fmt == 'none' or (fmt == '' and #numstr == 4 and numstr:match('^%d+$')) then

-- look for + or - preceding the number

return sign .. numstr

if n:find('[-+]') then

prefix,num = mw.ustring.match(n,'([-+])([%d.]+)')

else

num = n

end

end

-- Group number by integer and decimal parts.


-- integer and decimal parts of number

-- If there is no decimal part, delimit_groups returns only one table.

-- if there is no decimal part, delimit_groups only returns 1 table

local ipart, dpart = delimit_groups(numstr)

local result

local ipart, dpart = delimit_groups(num)

if fmt == 'commas' then

-- comma formatting

result = sign .. table.concat(ipart, numsep)

if fmt:lower() == 'commas' then

num = table.concat(ipart,',')

if dpart then

if dpart then

dpart = table.concat(dpart)

result = result .. numdot .. table.concat(dpart)

num = num..'.'..dpart

end

end

-- default to delimiting with .25em spaces

else

else

-- Delimit with a small gap by default.

num = {}

local groups = {}

num[1] = table.remove(ipart,1)

groups[1] = table.remove(ipart, 1)

for _, v in ipairs(ipart) do

for _, v in ipairs(ipart) do

table.insert(num,'<span style="margin-left:.25em">'..v..'</span>')

table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')

end

end

if dpart then

if dpart then

table.insert(num,'.'..table.remove(dpart,1))

table.insert(groups, numdot .. (table.remove(dpart, 1) or ''))

for _, v in ipairs(dpart) do

for _, v in ipairs(dpart) do

table.insert(num,'<span style="margin-left:.25em">'..v..'</span>')

table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')

end

end

end

end

num = table.concat(num)

result = sign .. table.concat(groups)

end

end

return result

end



local function sup_sub(sup, sub, align)

-- add prefix back if it had one

-- Return the same result as Module:Su except val defaults to align=right.

if prefix then

if align == 'l' or align == 'left' then

-- change hyphen to proper minus sign

if prefix == '-' then

align = 'left'

elseif align == 'c' or align == 'center' then

prefix = '&minus;'

align = 'center'

else

align = 'right'

end

return '<span style="display:inline-block;margin-bottom:-0.3em;vertical-align:-0.4em;line-height:1.2em;font-size:85%;text-align:' ..

align .. ';">' .. sup .. '<br />' .. sub .. '</span>'

end


local function range_text(items, unit_table, options)

local fmt = options.fmt

local nend = items.nend or ''

if items.isrepeat or unit_table.isangle then

nend = nend .. unit_table.text

end

local text = ''

for i = 1, #items do

if i % 2 == 0 then

text = text .. items[i]

else

text = text .. delimit(items[i].sign, items[i].clean, fmt) .. nend

end

end

num = prefix..num

end

end

return text

end



local function uncertainty_text(uncertainty, unit_table, options)

return tostring(num)

local angle, text, need_parens

if unit_table.isangle then

angle = unit_table.text

end

local upper = uncertainty.upper or {}

local lower = uncertainty.lower or {}

local uncU = upper.clean

if uncU then

local fmt = options.fmt

local uncL = lower.clean

if uncL then

uncU = delimit('+', uncU, fmt) .. (upper.errend or '')

uncL = delimit('−', uncL, fmt) .. (lower.errend or '')

if angle then

uncU = uncU .. angle

uncL = uncL .. angle

end

text = (angle or '') ..

'<span style="margin-left:0.3em;">' ..

sup_sub(uncU, uncL, options.align) ..

'</span>'

else

if upper.parens then

text = '(' .. uncU .. ')' -- old template did not delimit

else

text = (angle or '') ..

'<span style="margin-left:0.3em;margin-right:0.15em;">±</span>' ..

delimit('', uncU, fmt)

need_parens = true

end

if uncertainty.errend then

text = text .. uncertainty.errend

end

if angle then

text = text .. angle

end

end

else

if angle then

text = angle

end

end

return text, need_parens

end

end



local function _main(values, unit_spec, options)

-- Specific message for {{Val}} errors

if options.sandbox then

function valerror(msg,nocat)

data_module = data_module .. '/sandbox'

local ret = mw.html.create('strong')

convert_module = convert_module .. '/sandbox'

:addClass('error')

:wikitext('Error in &#123;&#123;Val&#125;&#125;: '..msg)

-- Not in talk, user, user_talk, or wikipedia_talk

if not nocat and not mw.title.getCurrentTitle():inNamespaces(1,2,3,5) then

ret:wikitext('[[Category:Pages with incorrect formatting templates use]]')

end

end

local action = options.action

return tostring(ret)

if action then

if action == 'list' then

-- Kludge: am using the align parameter (a=xxx) for type of list.

return list_units(options.align)

end

return valerror('invalid action "' .. action .. '".', options.nocat)

end

local number = values.number or (values.numbers and values.numbers[1]) or {}

local e_10 = options.e or {}

local novalue = (number.value == nil and e_10.clean == nil)

local fmt = options.fmt

local want_sort = true

local sortable = options.sortable

if sortable == 'off' or (sortable == nil and novalue) then

want_sort = false

elseif sortable == 'debug' then

-- Same as sortable = 'on' but the sort key is displayed.

else

sortable = 'on'

end

local sort_value = 1

if want_sort then

sort_value = number.value or 1

if e_10.value and sort_value ~= 0 then

-- The 'if' avoids {{val|0|e=1234}} giving an invalid sort_value due to overflow.

sort_value = sort_value * 10^e_10.value

end

end

local unit_table = makeunit(sort_value, {

u = unit_spec.u,

want_link = unit_spec.want_link,

per = unit_spec.per,

want_per_link = unit_spec.want_per_link,

nospace = novalue,

want_longscale = unit_spec.want_longscale,

sortable = sortable,

})

local sortkey

if unit_table then

if want_sort then

sortkey = unit_table.sortkey

end

else

unit_table = { text = '' }

if want_sort then

sortkey = convert_lookup('dummy', sort_value, nil, nil, nil, { sortable = sortable }).sortspan

end

end

local final_unit = unit_table.isangle and '' or unit_table.text

local e_text, n_text, need_parens

local uncertainty = values.uncertainty

if uncertainty then

if number.clean then

n_text = delimit(number.sign, number.clean, fmt) .. (number.nend or '')

local text

text, need_parens = uncertainty_text(uncertainty, unit_table, options)

if text then

n_text = n_text .. text

end

else

n_text = ''

end

else

if values.numbers.isrepeat then

final_unit = ''

end

n_text = range_text(values.numbers, unit_table, options)

need_parens = true

end

if e_10.clean then

if need_parens then

n_text = '(' .. n_text .. ')'

end

e_text = '10<sup>' .. delimit(e_10.sign, e_10.clean, fmt) .. '</sup>'

if number.clean then

e_text = '<span style="margin-left:0.25em;margin-right:0.15em;">×</span>' .. e_text

end

else

e_text = ''

end

local result =

(sortkey or '') ..

(options.prefix or '') ..

n_text ..

e_text ..

final_unit ..

(options.suffix or '')

if result ~= '' then

result = '<span class="nowrap">' .. result .. '</span>'

end

return result .. (options.warning or '')

end

end



local function check_parameters(args, has_ranges, nocat)

-- true/false whether or not the string is a valid number

-- Return warning text for the first problem parameter found, or nothing if ok.

-- ignores parentheses and parity symbolts

local whitelist = {

function validnumber(n)

a = true,

-- Look for a number that may be surrounded by parentheses or may have +/-

action = true,

n = mw.ustring.match(tostring(n),'^%(?[±%-%+]?([%d\.]+)%)?$')

debug = true,

return tonumber(n) ~= nil

e = true,

['end'] = true,

errend = true,

['+errend'] = true,

['-errend'] = true,

fmt = true,

['long scale'] = true,

long_scale = true,

longscale = true,

nocategory = true,

p = true,

s = true,

sortable = true,

u = true,

ul = true,

up = true,

upl = true,

}

for k, v in pairs(args) do

if type(k) == 'string' and not whitelist[k] then

local warning = string.format(mtext['mt-val-not-supported'], k, v)

return valerror(warning, nocat, true)

end

end

if not has_ranges and args[4] then

return valerror(mtext['mt-ignore-parameter4'], nocat, true)

end

end


local function main(frame)

local getArgs = require('Module:Arguments').getArgs

local args = getArgs(frame, {wrappers = { 'Template:Val' }})

local nocat = args.nocategory

local numbers = {} -- table of number tables, perhaps with range text

local msg = get_args(numbers, args)

if msg then

return valerror(msg, nocat)

end

if args.u and args.ul then

return valerror(mtext['mt-both-u-ul'], nocat)

end

if args.up and args.upl then

return valerror(mtext['mt-both-up-upl'], nocat)

end

local values

if numbers.has_ranges then

-- Multiple values with range separators but no uncertainty.

numbers.nend = args['end']

values = {

numbers = numbers,

}

else

-- A single value with optional uncertainty.

local function setfield(i, dst, src)

local v = args[src]

if v then

if numbers[i] then

numbers[i][dst] = v

else

numbers[i] = { [dst] = v }

end

end

end

setfield(1, 'nend', 'end')

setfield(2, 'errend', '+errend')

setfield(3, 'errend', '-errend')

values = {

number = numbers[1],

uncertainty = {

upper = numbers[2],

lower = numbers[3],

errend = args.errend,

}

}

end

local unit_spec = {

u = args.ul or args.u,

want_link = args.ul ~= nil,

per = args.upl or args.up,

want_per_link = args.upl ~= nil,

want_longscale = (args.longscale or args.long_scale or args['long scale']) == 'on',

}

local options = {

action = args.action,

align = args.a,

e = numbers.e,

fmt = args.fmt,

nocat = nocat,

prefix = args.p,

sandbox = string.find(frame:getTitle(), 'sandbox', 1, true) ~= nil,

sortable = args.sortable or (args.debug == 'yes' and 'debug' or nil),

suffix = args.s,

warning = check_parameters(args, numbers.has_ranges, nocat),

}

return _main(values, unit_spec, options)

end

end



return { main = main, _main = _main }

return p


Latest revision as of 03:19, 30 June 2022

This module implements {{Val}}.

The following modules are developed:

Use {{val/sandbox}} for testing, for example:

-- For Template:Val, output a number and optional unit.
-- Format options include scientific and uncertainty notations.

local numdot = '.'  -- decimal mark (use ',' for Italian)
local numsep = ','  -- group separator (use ' ' for Italian)
local mtext = {
 -- Message and other text that should be localized.
 ['mt-bad-exponent'] =       'exponent parameter (<b>e</b>)',
 ['mt-parameter'] =          'parameter ',
 ['mt-not-number'] =         'is not a valid number',
 ['mt-cannot-range'] =       'cannot use a range if the first parameter includes "e"',
 ['mt-need-range'] =         'needs a range in parameter 2',
 ['mt-should-range'] =       'should be a range',
 ['mt-cannot-with-e'] =      'cannot be used if the first parameter includes "e"',
 ['mt-not-range'] =          'does not accept a range',
 ['mt-cannot-e'] =           'cannot use e notation',
 ['mt-too-many-parameter'] = 'too many parameters',
 ['mt-need-number'] =        'need a number after the last parameter because it is a range.',
 ['mt-ignore-parameter4'] =  'Val parameter 4 ignored',
 ['mt-val-not-supported'] =  'Val parameter "%s=%s" is not supported',
 ['mt-invalid-scale'] =      'Unit "%s" has invalid scale "%s"',
 ['mt-both-u-ul'] =          'unit (<b>u</b>) and unit with link (<b>ul</b>) are both specified, only one is allowed.',
 ['mt-both-up-upl'] =        'unit per (<b>up</b>) and unit per with link (<b>upl</b>) are both specified, only one is allowed.',
}

local data_module = 'Module:Val/units'
local convert_module = 'Module:Convert'

local function valerror(msg, nocat, iswarning)
 -- Return formatted message text for an error or warning.
 -- Can append "#FormattingError" to URL of a page with a problem to find it.
 local anchor = '<span id="FormattingError"></span>'
 local body, category
 if nocat or mw.title.getCurrentTitle():inNamespaces(1, 2, 3, 5) then
  -- No category in Talk, User, User_talk, or Wikipedia_talk.
  category = ''
 else
  category = '[[Category:Pages with incorrect formatting templates use]]'
 end
 iswarning = false  -- problems are infrequent so try showing large error so editor will notice
 if iswarning then
  body = '<sup class="noprint Inline-Template" style="white-space:nowrap;">' ..
   '[[Template:Val|<span title="' ..
   msg:gsub('"', '&quot;') ..
   '">warning</span>]]</sup>'
 else
  body = '<strong class="error">' ..
   'Error in &#123;&#123;[[Template:val|val]]&#125;&#125;: ' ..
   msg ..
   '</strong>'
 end
 return anchor .. body .. category
end

local range_types = {
 -- No need for '&nbsp;' because nowrap applies to all output.
 [","]   = ", ",
 ["by"]  = " by ",
 ["-"]   = "–",
 ["–"]   = "–",
 ["and"] = " and ",
 ["or"]  = " or " ,
 ["to"]  = " to " ,
 ["x"]   = " × ",
 ["×"]   = " × ",
 ["/"]   = "/",
}
local range_repeat_unit = {
 -- WP:UNIT wants unit repeated when a "multiply" range is used.
 ["x"]   = true,
 ["×"]   = true,
}

local function extract_item(index, numbers, arg)
 -- Extract an item from arg and store the result in numbers[index].
 -- If no argument or if argument is valid, return nil (no error);
 -- otherwise, return an error message.
 -- The stored result is:
 -- * a table for a number (empty if there was no specified number); or
 -- * a string for range text
 -- Input like 1e3 is regarded as invalid for all except argument 1
 -- which accepts e notation as an alternative to the 'e' argument.
 -- Input group separators are removed.
 local which = index
 local function fail(msg)
  local description
  if which == 'e' then
   description = mtext['mt-bad-exponent']
  else
   description = mtext['mt-parameter'] .. which
  end
  return description .. ' ' .. (msg or mtext['mt-not-number']) .. '.'
 end
 local result = {}
 local range = range_types[arg]
 if range then
  if type(index) == 'number' and (index % 2 == 0) then
   if index == 2 then
    if numbers[1] and numbers[1].exp then
     return fail(mtext['mt-cannot-range'])
    end
    numbers.has_ranges = true
   else
    if not numbers.has_ranges then
     return fail(mtext['mt-need-range'])
    end
   end
   numbers[index] = range
   if range_repeat_unit[arg] then
    -- Any "repeat" range forces unit (if any) to be repeated for all items.
    numbers.isrepeat = true
   end
   return nil
  end
  return fail(mtext['mt-not-range'])
 end
 if numbers.has_ranges and type(index) == 'number' and (index % 2 == 0) then
  return fail(mtext['mt-should-range'])
 end
 if index == 'e' then
  local e = numbers[1] and numbers[1].exp
  if e then
   if arg then
    return fail(mtext['mt-cannot-with-e'])
   end
   arg = e
   which = 1
  end
 end
 if arg and arg ~= '' then
  arg = arg:gsub(numsep, '')
  if numdot ~= '.' then
   arg = arg:gsub(numdot, '.')
  end
  if arg:sub(1, 1) == '(' and arg:sub(-1) == ')' then
   result.parens = true
   arg = arg:sub(2, -2)
  end
  local a, b = arg:match('^(.+)[Ee](.+)$')
  if a then
   if index == 1 then
    arg = a
    result.exp = b
   else
    return fail(mtext['mt-cannot-e'])
   end
  end
  local isnegative, propersign, prefix
  local minus = '−'
  prefix, arg = arg:match('^(.-)([%d.]+)$')
  local value = tonumber(arg)
  if not value then
   return fail()
  end
  if arg:sub(1, 1) == '.' then
   arg = '0' .. arg
  end
  if prefix == '' then
   -- Ignore.
  elseif prefix == '±' then
   -- Display for first number, ignore for others.
   if index == 1 then
    propersign = '±'
   end
  elseif prefix == '+' then
   propersign = '+'
  elseif prefix == '-' or prefix == minus then
   propersign = minus
   isnegative = true
  else
   return fail()
  end
  result.clean = arg
  result.sign = propersign or ''
  result.value = isnegative and -value or value
 end
 numbers[index] = result
 return nil  -- no error
end

local function get_args(numbers, args)
 -- Extract arguments and store the results in numbers.
 -- Return nothing (no error) if ok; otherwise, return an error message.
 for index = 1, 99 do
  local which = index
  local arg = args[which]  -- has been trimmed
  if not arg then
   which = 'e'
   arg = args[which]
  end
  local msg = extract_item(which, numbers, arg)
  if msg then
   return msg
  end
  if which == 'e' then
   break
  end
  if index > 19 then
   return mtext['mt-too-many-parameter']
  end
 end
 if numbers.has_ranges and (#numbers % 2 == 0) then
  return mtext['mt-need-number']
 end
end

local function get_scale(text, ucode)
 -- Return the value of text as a number, or throw an error.
 -- This supports extremely basic expressions of the form:
 --   a / b
 --   a ^ b
 -- where a and b are numbers or 'pi'.
 local n = tonumber(text)
 if n then
  return n
 end
 n = text:gsub('pi', math.pi)
 for _, op in ipairs({ '/', '^' }) do
  local a, b = n:match('^(.-)' .. op .. '(.*)$')
  if a then
   a = tonumber(a)
   b = tonumber(b)
   if a and b then
    if op == '/' then
     return a / b
    elseif op == '^' then
     return a ^ b
    end
   end
   break
  end
 end
 error(string.format(mtext['mt-invalid-scale'], ucode, text))
end

local function get_builtin_unit(ucode, definitions)
 -- Return table of information for the specified built-in unit, or nil if not known.
 -- Each defined unit code must be followed by two spaces (not tab characters).
 local _, pos = definitions:find('\n' .. ucode .. '  ', 1, true)
 if pos then
  local endline = definitions:find('%s*\n', pos)
  if endline then
   local result = {}
   local n = 0
   local text = definitions:sub(pos + 1, endline - 1):gsub('%s%s+', '\t')
   for item in (text .. '\t'):gmatch('(%S.-)\t') do
    if item == 'ALIAS' then
     result.alias = true
    elseif item == 'ANGLE' then
     result.isangle = true
     result.nospace = true
    elseif item == 'NOSPACE' then
     result.nospace = true
    elseif item == 'SI' then
     result.si = true
    else
     n = n + 1
     if n == 1 then
      local link, symbol = item:match('^%[%[([^|]+)|(.+)%]%]$')
      if link then
       result.symbol = symbol
       result.link = link
       n = 2
      else
       result.symbol = item
      end
     elseif n == 2 then
      result.link = item
     elseif n == 3 then
      result.scale_text = item
      result.scale = get_scale(item, ucode)
     else
      result.more_ignored = item
      break
     end
    end
   end
   if result.si then
    local s = result.symbol
    if ucode == 'mc' .. s or ucode == 'mu' .. s then
     result.ucode = 'µ' .. s  -- unit code for convert should be this
    end
   end
   if n >= 2 or (n >= 1 and result.alias) then
    return result
   end
   -- Ignore invalid definition, treating it as a comment.
  end
 end
end

local function convert_lookup(ucode, value, scaled_top, want_link, si, options)
 local lookup = require(convert_module)._unit
 return lookup(ucode, {
   value = value,
   scaled_top = scaled_top,
   link = want_link,
   si = si,
   sort = options.sortable,
  })
end

local function get_unit(ucode, value, scaled_top, options)
 local want_link = options.want_link
 if scaled_top then
  want_link = options.want_per_link
 end
 local data = mw.loadData(data_module)
 local result = options.want_longscale and
  get_builtin_unit(ucode, data.builtin_units_long_scale) or
  get_builtin_unit(ucode, data.builtin_units)
 local si, use_convert
 if result then
  if result.alias then
   ucode = result.symbol
   use_convert = true
  end
  if result.scale then
   -- Setting si means convert will use the unit as given, and the sort key
   -- will be calculated from the value without any extra scaling that may
   -- occur if convert found the unit code. For example, if val defines the
   -- unit 'year' with a scale and if si were not set, convert would also apply
   -- its own scale because convert knows that a year is 31,557,600 seconds.
   si = { result.symbol, result.link }
   value = value * result.scale
  end
  if result.si then
   ucode = result.ucode or ucode
   si = { result.symbol, result.link }
   use_convert = true
  end
 else
  result = {}
  use_convert = true
 end
 local convert_unit = convert_lookup(ucode, value, scaled_top, want_link, si, options)
 result.sortkey = convert_unit.sortspan
 if use_convert then
  result.text = convert_unit.text
  result.scaled_top = convert_unit.scaled_value
 else
  if want_link then
   result.text = '[[' .. result.link .. '|' .. result.symbol .. ']]'
  else
   result.text = result.symbol
  end
  result.scaled_top = value
 end
 return result
end

local function makeunit(value, options)
 -- Return table of information for the requested unit and options, or
 -- return nil if no unit.
 options = options or {}
 local unit
 local ucode = options.u
 local percode = options.per
 if ucode then
  unit = get_unit(ucode, value, nil, options)
 elseif percode then
  unit = { nospace = true, scaled_top = value }
 else
  return nil
 end
 local text = unit.text or ''
 local sortkey = unit.sortkey
 if percode then
  local function bracketed(code, text)
   return code:find('[*./]') and '(' .. text .. ')' or text
  end
  local perunit = get_unit(percode, 1, unit.scaled_top, options)
  text = (ucode and bracketed(ucode, text) or '') ..
    '/' .. bracketed(percode, perunit.text)
  sortkey = perunit.sortkey
 end
 if not (unit.nospace or options.nospace) then
  text = '&nbsp;' .. text
 end
 return { text = text, isangle = unit.isangle, sortkey = sortkey }
end

local function list_units(mode)
 -- Return wikitext to list the built-in units.
 -- A unit code should not contain wikimarkup so don't bother escaping.
 local data = mw.loadData(data_module)
 local definitions = data.builtin_units .. data.builtin_units_long_scale
 local last_was_blank = true
 local n = 0
 local result = {}
 local function add(line)
  if line == '' then
   last_was_blank = true
  else
   if last_was_blank and n > 0 then
    n = n + 1
    result[n] = ''
   end
   last_was_blank = false
   n = n + 1
   result[n] = line
  end
 end
 local si_prefixes = {
  -- These are the prefixes recognized by convert; u is accepted for micro.
  y = 'y',
  z = 'z',
  a = 'a',
  f = 'f',
  p = 'p',
  n = 'n',
  u = 'µ',
  ['µ'] = 'µ',
  m = 'm',
  c = 'c',
  d = 'd',
  da = 'da',
  h = 'h',
  k = 'k',
  M = 'M',
  G = 'G',
  T = 'T',
  P = 'P',
  E = 'E',
  Z = 'Z',
  Y = 'Y',
 }
 local function is_valid(ucode, unit)
  if unit and not unit.more_ignored then
   assert(type(unit.symbol) == 'string' and unit.symbol ~= '')
   if unit.alias then
    if unit.link or unit.scale_text or unit.si then
     return false
    end
   end
   if unit.si then
    if unit.scale_text then
     return false
    end
    ucode = unit.ucode or ucode
    local base = unit.symbol
    if ucode == base then
     unit.display = base
     return true
    end
    local plen = #ucode - #base
    if plen > 0 then
     local prefix = si_prefixes[ucode:sub(1, plen)]
     if prefix and ucode:sub(plen + 1) == base then
      unit.display = prefix .. base
      return true
     end
    end
   else
    unit.display = unit.symbol
    return true
   end
  end
  return false
 end
 local lookup = require(convert_module)._unit
 local function show_convert(ucode, unit)
  -- If a built-in unit defines a scale or sets the SI flag, any unit defined in
  -- convert is not used (the scale or SI prefix's scale is used for a sort key).
  -- If there is no scale or SI flag, and the unit is not defined in convert,
  -- the sort key may not be correct; this allows such units to be identified.
  if not (unit.si or unit.scale_text) then
   if mode == 'convert' then
    unit.show = not lookup(unit.alias and unit.symbol or ucode).unknown
    unit.show_text = 'CONVERT'
   elseif mode == 'unknown' then
    unit.show = lookup(unit.alias and unit.symbol or ucode).unknown
    unit.show_text = 'UNKNOWN'
   elseif not unit.alias then
    -- Show convert's scale in square brackets ('[1]' for an unknown unit).
    -- Don't show scale for an alias because it's misleading for temperature
    -- and an alias is probably not useful for anything else.
    local scale = lookup(ucode, {value=1, sort='on'}).scaled_value
    if type(scale) == 'number' then
     scale = string.format('%.5g', scale):gsub('e%+?(%-?)0*(%d+)', 'e%1%2')
    else
     scale = '?'
    end
    unit.show = true
    unit.show_text = '[' .. scale .. ']'
   end
  end
 end
 for line in definitions:gmatch('([^\n]*)\n') do
  local pos, _ = line:find('  ', 1, true)
  if pos then
   local ucode = line:sub(1, pos - 1)
   local unit = get_builtin_unit(ucode, '\n' .. line .. '\n')
   if is_valid(ucode, unit) then
    show_convert(ucode, unit)
    local flags, text
    if unit.alias then
     text = unit.symbol
    else
     text = '[[' .. unit.link .. '|' .. unit.display .. ']]'
    end
    if unit.isangle then
     unit.nospace = nil  -- don't show redundant flag
    end
    for _, f in ipairs({
      { 'alias', 'ALIAS' },
      { 'isangle', 'ANGLE' },
      { 'nospace', 'NOSPACE' },
      { 'si', 'SI' },
      { 'scale_text', unit.scale_text },
      { 'show', unit.show_text },
     }) do
     if unit[f[1]] then
      local t = f[2]
      if t:match('^%u+$') then
       t = '<small>' .. t .. '</small>'
      end
      if flags then
       flags = flags .. ' ' .. t
      else
       flags = t
      end
     end
    end
    if flags then
     text = text .. ' • ' .. flags
    end
    add(ucode .. ' = ' .. text .. '<br />')
   else
    add(line .. ' ◆ <b>invalid definition</b><br />')
   end
  else
   add(line)
  end
 end
 return table.concat(result, '\n')
end

local delimit_groups = require('Module:Gapnum').groups
local function delimit(sign, numstr, fmt)
 -- Return sign and numstr (unsigned digits or numdot only) after formatting.
 -- Four-digit integers are not formatted with gaps.
 fmt = (fmt or ''):lower()
 if fmt == 'none' or (fmt == '' and #numstr == 4 and numstr:match('^%d+$')) then
  return sign .. numstr
 end
 -- Group number by integer and decimal parts.
 -- If there is no decimal part, delimit_groups returns only one table.
 local ipart, dpart = delimit_groups(numstr)
 local result
 if fmt == 'commas' then
  result = sign .. table.concat(ipart, numsep)
  if dpart then
   result = result .. numdot .. table.concat(dpart)
  end
 else
  -- Delimit with a small gap by default.
  local groups = {}
  groups[1] = table.remove(ipart, 1)
  for _, v in ipairs(ipart) do
   table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')
  end
  if dpart then
   table.insert(groups, numdot .. (table.remove(dpart, 1) or ''))
   for _, v in ipairs(dpart) do
    table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')
   end
  end
  result = sign .. table.concat(groups)
 end
 return result
end

local function sup_sub(sup, sub, align)
 -- Return the same result as Module:Su except val defaults to align=right.
 if align == 'l' or align == 'left' then
  align = 'left'
 elseif align == 'c' or align == 'center' then
  align = 'center'
 else
  align = 'right'
 end
 return '<span style="display:inline-block;margin-bottom:-0.3em;vertical-align:-0.4em;line-height:1.2em;font-size:85%;text-align:' ..
  align .. ';">' .. sup .. '<br />' .. sub .. '</span>'
end

local function range_text(items, unit_table, options)
 local fmt = options.fmt
 local nend = items.nend or ''
 if items.isrepeat or unit_table.isangle then
  nend = nend .. unit_table.text
 end
 local text = ''
 for i = 1, #items do
  if i % 2 == 0 then
   text = text .. items[i]
  else
   text = text .. delimit(items[i].sign, items[i].clean, fmt) .. nend
  end
 end
 return text
end

local function uncertainty_text(uncertainty, unit_table, options)
 local angle, text, need_parens
 if unit_table.isangle then
  angle = unit_table.text
 end
 local upper = uncertainty.upper or {}
 local lower = uncertainty.lower or {}
 local uncU = upper.clean
 if uncU then
  local fmt = options.fmt
  local uncL = lower.clean
  if uncL then
   uncU = delimit('+', uncU, fmt) .. (upper.errend or '')
   uncL = delimit('−', uncL, fmt) .. (lower.errend or '')
   if angle then
    uncU = uncU .. angle
    uncL = uncL .. angle
   end
   text = (angle or '') ..
    '<span style="margin-left:0.3em;">' ..
    sup_sub(uncU, uncL, options.align) ..
    '</span>'
  else
   if upper.parens then
    text = '(' .. uncU .. ')'  -- old template did not delimit
   else
    text = (angle or '') ..
     '<span style="margin-left:0.3em;margin-right:0.15em;">±</span>' ..
     delimit('', uncU, fmt)
    need_parens = true
   end
   if uncertainty.errend then
    text = text .. uncertainty.errend
   end
   if angle then
    text = text .. angle
   end
  end
 else
  if angle then
   text = angle
  end
 end
 return text, need_parens
end

local function _main(values, unit_spec, options)
 if options.sandbox then
  data_module = data_module .. '/sandbox'
  convert_module = convert_module .. '/sandbox'
 end
 local action = options.action
 if action then
  if action == 'list' then
   -- Kludge: am using the align parameter (a=xxx) for type of list.
   return list_units(options.align)
  end
  return valerror('invalid action "' .. action .. '".', options.nocat)
 end
 local number = values.number or (values.numbers and values.numbers[1]) or {}
 local e_10 = options.e or {}
 local novalue = (number.value == nil and e_10.clean == nil)
 local fmt = options.fmt
 local want_sort = true
 local sortable = options.sortable
 if sortable == 'off' or (sortable == nil and novalue) then
  want_sort = false
 elseif sortable == 'debug' then
  -- Same as sortable = 'on' but the sort key is displayed.
 else
  sortable = 'on'
 end
 local sort_value = 1
 if want_sort then
  sort_value = number.value or 1
  if e_10.value and sort_value ~= 0 then
   -- The 'if' avoids {{val|0|e=1234}} giving an invalid sort_value due to overflow.
   sort_value = sort_value * 10^e_10.value
  end
 end
 local unit_table = makeunit(sort_value, {
      u = unit_spec.u,
      want_link = unit_spec.want_link,
      per = unit_spec.per,
      want_per_link = unit_spec.want_per_link,
      nospace = novalue,
      want_longscale = unit_spec.want_longscale,
      sortable = sortable,
     })
 local sortkey
 if unit_table then
  if want_sort then
   sortkey = unit_table.sortkey
  end
 else
  unit_table = { text = '' }
  if want_sort then
   sortkey = convert_lookup('dummy', sort_value, nil, nil, nil, { sortable = sortable }).sortspan
  end
 end
 local final_unit = unit_table.isangle and '' or unit_table.text
 local e_text, n_text, need_parens
 local uncertainty = values.uncertainty
 if uncertainty then
  if number.clean then
   n_text = delimit(number.sign, number.clean, fmt) .. (number.nend or '')
   local text
   text, need_parens = uncertainty_text(uncertainty, unit_table, options)
   if text then
    n_text = n_text .. text
   end
  else
   n_text = ''
  end
 else
  if values.numbers.isrepeat then
   final_unit = ''
  end
  n_text = range_text(values.numbers, unit_table, options)
  need_parens = true
 end
 if e_10.clean then
  if need_parens then
   n_text = '(' .. n_text .. ')'
  end
  e_text = '10<sup>' .. delimit(e_10.sign, e_10.clean, fmt) .. '</sup>'
  if number.clean then
   e_text = '<span style="margin-left:0.25em;margin-right:0.15em;">×</span>' .. e_text
  end
 else
  e_text = ''
 end
 local result =
  (sortkey or '') ..
  (options.prefix or '') ..
  n_text ..
  e_text ..
  final_unit ..
  (options.suffix or '')
 if result ~= '' then
  result = '<span class="nowrap">' .. result .. '</span>'
 end
 return result .. (options.warning or '')
end

local function check_parameters(args, has_ranges, nocat)
 -- Return warning text for the first problem parameter found, or nothing if ok.
 local whitelist = {
  a = true,
  action = true,
  debug = true,
  e = true,
  ['end'] = true,
  errend = true,
  ['+errend'] = true,
  ['-errend'] = true,
  fmt = true,
  ['long scale'] = true,
  long_scale = true,
  longscale = true,
  nocategory = true,
  p = true,
  s = true,
  sortable = true,
  u = true,
  ul = true,
  up = true,
  upl = true,
 }
 for k, v in pairs(args) do
  if type(k) == 'string' and not whitelist[k] then
   local warning = string.format(mtext['mt-val-not-supported'], k, v)
   return valerror(warning, nocat, true)
  end
 end
 if not has_ranges and args[4] then
  return valerror(mtext['mt-ignore-parameter4'], nocat, true)
 end
end

local function main(frame)
 local getArgs = require('Module:Arguments').getArgs
 local args = getArgs(frame, {wrappers = { 'Template:Val' }})
 local nocat = args.nocategory
 local numbers = {}  -- table of number tables, perhaps with range text
 local msg = get_args(numbers, args)
 if msg then
  return valerror(msg, nocat)
 end
 if args.u and args.ul then
  return valerror(mtext['mt-both-u-ul'], nocat)
 end
 if args.up and args.upl then
  return valerror(mtext['mt-both-up-upl'], nocat)
 end
 local values
 if numbers.has_ranges then
  -- Multiple values with range separators but no uncertainty.
  numbers.nend = args['end']
  values = {
   numbers = numbers,
  }
 else
  -- A single value with optional uncertainty.
  local function setfield(i, dst, src)
   local v = args[src]
   if v then
    if numbers[i] then
     numbers[i][dst] = v
    else
     numbers[i] = { [dst] = v }
    end
   end
  end
  setfield(1, 'nend', 'end')
  setfield(2, 'errend', '+errend')
  setfield(3, 'errend', '-errend')
  values = {
   number = numbers[1],
   uncertainty = {
    upper = numbers[2],
    lower = numbers[3],
    errend = args.errend,
   }
  }
 end
 local unit_spec = {
   u = args.ul or args.u,
   want_link = args.ul ~= nil,
   per = args.upl or args.up,
   want_per_link = args.upl ~= nil,
   want_longscale = (args.longscale or args.long_scale or args['long scale']) == 'on',
  }
 local options = {
   action = args.action,
   align = args.a,
   e = numbers.e,
   fmt = args.fmt,
   nocat = nocat,
   prefix = args.p,
   sandbox = string.find(frame:getTitle(), 'sandbox', 1, true) ~= nil,
   sortable = args.sortable or (args.debug == 'yes' and 'debug' or nil),
   suffix = args.s,
   warning = check_parameters(args, numbers.has_ranges, nocat),
  }
 return _main(values, unit_spec, options)
end

return { main = main, _main = _main }

Retrieved from "https://en.wikipedia.org/w/index.php?title=Module:Val&oldid=1095746275"

Hidden category: 
Wikipedia template-protected modules
 



This page was last edited on 30 June 2022, at 03:19 (UTC).

Text is available under the Creative Commons Attribution-ShareAlike License 4.0; additional terms may apply. By using this site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.



Privacy policy

About Wikipedia

Disclaimers

Contact Wikipedia

Code of Conduct

Developers

Statistics

Cookie statement

Mobile view



Wikimedia Foundation
Powered by MediaWiki